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 b9b22b2a feat(java): type meta encoding for xlang in java (#2197)
b9b22b2a is described below
commit b9b22b2aa7d6f6b85341e89d46251cba70e3b180
Author: Shawn Yang <[email protected]>
AuthorDate: Mon May 12 01:21:45 2025 +0800
feat(java): type meta encoding for xlang in java (#2197)
## What does this PR do?
This PR implements type meta encoding for xlang in java and refined the
tyoe meta for java based new spec in #2216
## Related issues
<!--
Is there any related issue? Please attach here.
- #xxxx0
- #xxxx1
- #xxxx2
-->
## Does this PR introduce any user-facing change?
<!--
If any user-facing interface changes, please [open an
issue](https://github.com/apache/fury/issues/new/choose) describing the
need to do so and update the document if necessary.
-->
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
<!--
When the PR has an impact on performance (if you don't know whether the
PR will have an impact on performance, you can submit the PR first, and
if it will have impact on performance, the code reviewer will explain
it), be sure to attach a benchmark data here.
-->
---
.../src/main/java/org/apache/fury/Fury.java | 5 +
.../fury/builder/BaseObjectCodecBuilder.java | 11 +-
.../fury/builder/CompatibleCodecBuilder.java | 1 +
.../main/java/org/apache/fury/meta/ClassDef.java | 396 +++++++++++++++------
.../java/org/apache/fury/meta/ClassDefDecoder.java | 72 ++--
.../java/org/apache/fury/meta/ClassDefEncoder.java | 98 ++---
.../java/org/apache/fury/meta/TypeDefDecoder.java | 110 ++++++
.../java/org/apache/fury/meta/TypeDefEncoder.java | 182 ++++++++++
.../java/org/apache/fury/meta/TypeExtMeta.java | 10 +-
.../org/apache/fury/resolver/ClassResolver.java | 19 +-
.../org/apache/fury/resolver/TypeResolver.java | 9 +
.../org/apache/fury/resolver/XtypeResolver.java | 125 ++++++-
.../src/main/java/org/apache/fury/type/Types.java | 62 ++++
.../org/apache/fury/meta/ClassDefEncoderTest.java | 11 +-
.../java/org/apache/fury/meta/ClassDefTest.java | 22 +-
.../java/org/apache/fury/meta/TypeDefTest.java | 63 ++++
.../org/apache/fury/meta/ClassDefEncoderTest.java | 2 +-
17 files changed, 975 insertions(+), 223 deletions(-)
diff --git a/java/fury-core/src/main/java/org/apache/fury/Fury.java
b/java/fury-core/src/main/java/org/apache/fury/Fury.java
index 81d437d7..f40a1fd0 100644
--- a/java/fury-core/src/main/java/org/apache/fury/Fury.java
+++ b/java/fury-core/src/main/java/org/apache/fury/Fury.java
@@ -43,6 +43,7 @@ import org.apache.fury.logging.Logger;
import org.apache.fury.logging.LoggerFactory;
import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.memory.MemoryUtils;
+import org.apache.fury.meta.MetaCompressor;
import org.apache.fury.resolver.ClassInfo;
import org.apache.fury.resolver.ClassInfoHolder;
import org.apache.fury.resolver.ClassResolver;
@@ -1717,6 +1718,10 @@ public final class Fury implements BaseFury {
return config.compressLong();
}
+ public MetaCompressor getMetaCompressor() {
+ return config.getMetaCompressor();
+ }
+
public static FuryBuilder builder() {
return new FuryBuilder();
}
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 aae2f236..da8ac503 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
@@ -547,9 +547,14 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
// Preconditions.checkArgument(isMonomorphic(cls), cls);
Reference serializerRef = serializerMap.get(cls);
if (serializerRef == null) {
- // potential recursive call for seq codec generation is handled in
`getSerializerClass`.
- Class<? extends Serializer> serializerClass =
- visitFury(f -> f.getClassResolver().getSerializerClass(cls));
+ Class<? extends Serializer> serializerClass;
+ if (fury.isCrossLanguage()) {
+ // xlang will take all map/collection interface as monomorphic
+ serializerClass = visitFury(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));
+ }
Preconditions.checkNotNull(serializerClass, "Unsupported for class " +
cls);
if (!ReflectionUtils.isPublic(serializerClass)) {
// TODO(chaokunyang) add jdk17+ unexported class check.
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 d49ffc61..f799a708 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
@@ -128,6 +128,7 @@ public class CompatibleCodecBuilder extends
BaseObjectCodecBuilder {
@Override
protected boolean isMonomorphic(Class<?> clz) {
+ Preconditions.checkArgument(!fury.isCrossLanguage());
return ReflectionUtils.isMonomorphic(clz);
}
diff --git a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java
b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java
index a5d539c8..1d330e6a 100644
--- a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java
+++ b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java
@@ -31,6 +31,8 @@ import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@@ -49,13 +51,17 @@ import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.memory.Platform;
import org.apache.fury.reflect.ReflectionUtils;
import org.apache.fury.reflect.TypeRef;
+import org.apache.fury.resolver.ClassInfo;
import org.apache.fury.resolver.ClassResolver;
+import org.apache.fury.resolver.TypeResolver;
+import org.apache.fury.resolver.XtypeResolver;
import org.apache.fury.serializer.CompatibleSerializer;
import org.apache.fury.serializer.NonexistentClass;
import org.apache.fury.type.Descriptor;
import org.apache.fury.type.FinalObjectTypeStub;
import org.apache.fury.type.GenericType;
import org.apache.fury.type.TypeUtils;
+import org.apache.fury.type.Types;
import org.apache.fury.util.Preconditions;
/**
@@ -76,12 +82,12 @@ import org.apache.fury.util.Preconditions;
* @see ReflectionUtils#getFieldOffset
*/
public class ClassDef implements Serializable {
+ static final int COMPRESS_META_FLAG = 0b1 << 13;
+ static final int HAS_FIELDS_META_FLAG = 0b1 << 12;
+ static final int META_SIZE_MASKS = 0b111_1111_1111;
+ static final int NUM_HASH_BITS = 50;
private static final Logger LOG = LoggerFactory.getLogger(ClassDef.class);
- static final int SCHEMA_COMPATIBLE_FLAG = 0b10000;
- public static final int SIZE_TWO_BYTES_FLAG = 0b100000;
- static final int OBJECT_TYPE_FLAG = 0b1000000;
- static final int COMPRESSION_FLAG = 0b10000000;
// TODO use field offset to sort field, which will hit l1-cache more. Since
// `objectFieldOffset` is not part of jvm-specification, it may change
between different jdk
// vendor. But the deserialization peer use the class definition to create
deserializer, it's OK
@@ -108,7 +114,7 @@ public class ClassDef implements Serializable {
private final ClassSpec classSpec;
private final List<FieldInfo> fieldsInfo;
- private final boolean isObjectType;
+ private final boolean hasFieldsMeta;
// Unique id for class def. If class def are same between processes, then
the id will
// be same too.
private final long id;
@@ -118,16 +124,24 @@ public class ClassDef implements Serializable {
ClassDef(
ClassSpec classSpec,
List<FieldInfo> fieldsInfo,
- boolean isObjectType,
+ boolean hasFieldsMeta,
long id,
byte[] encoded) {
this.classSpec = classSpec;
this.fieldsInfo = fieldsInfo;
- this.isObjectType = isObjectType;
+ this.hasFieldsMeta = hasFieldsMeta;
this.id = id;
this.encoded = encoded;
}
+ public static void skipClassDef(MemoryBuffer buffer, long id) {
+ int size = (int) (id & META_SIZE_MASKS);
+ if (size == META_SIZE_MASKS) {
+ size += buffer.readVarUint32Small14();
+ }
+ buffer.increaseReaderIndex(size);
+ }
+
/**
* Returns class name.
*
@@ -147,8 +161,8 @@ public class ClassDef implements Serializable {
}
/** Returns ext meta for the class. */
- public boolean isObjectType() {
- return isObjectType;
+ public boolean hasFieldsMeta() {
+ return hasFieldsMeta;
}
/**
@@ -165,16 +179,14 @@ public class ClassDef implements Serializable {
@Override
public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
if (o == null || getClass() != o.getClass()) {
return false;
}
ClassDef classDef = (ClassDef) o;
- return Objects.equals(classSpec.entireClassName,
classDef.classSpec.entireClassName)
- && Objects.equals(fieldsInfo, classDef.fieldsInfo)
- && Objects.equals(id, classDef.id);
+ return hasFieldsMeta == classDef.hasFieldsMeta
+ && id == classDef.id
+ && Objects.equals(classSpec, classDef.classSpec)
+ && Objects.equals(fieldsInfo, classDef.fieldsInfo);
}
@Override
@@ -190,8 +202,8 @@ public class ClassDef implements Serializable {
+ '\''
+ ", fieldsInfo="
+ fieldsInfo
- + ", isObjectType="
- + isObjectType
+ + ", hasFieldsMeta="
+ + hasFieldsMeta
+ ", id="
+ id
+ '}';
@@ -203,14 +215,19 @@ public class ClassDef implements Serializable {
}
/** Read class definition from buffer. */
- public static ClassDef readClassDef(ClassResolver classResolver,
MemoryBuffer buffer) {
- return ClassDefDecoder.decodeClassDef(classResolver, buffer,
buffer.readInt64());
+ public static ClassDef readClassDef(Fury fury, MemoryBuffer buffer) {
+ if (fury.isCrossLanguage()) {
+ return TypeDefDecoder.decodeClassDef(fury.getXtypeResolver(), buffer,
buffer.readInt64());
+ }
+ return ClassDefDecoder.decodeClassDef(fury.getClassResolver(), buffer,
buffer.readInt64());
}
/** Read class definition from buffer. */
- public static ClassDef readClassDef(
- ClassResolver classResolver, MemoryBuffer buffer, long header) {
- return ClassDefDecoder.decodeClassDef(classResolver, buffer, header);
+ public static ClassDef readClassDef(Fury fury, MemoryBuffer buffer, long
header) {
+ if (fury.isCrossLanguage()) {
+ return TypeDefDecoder.decodeClassDef(fury.getXtypeResolver(), buffer,
header);
+ }
+ return ClassDefDecoder.decodeClassDef(fury.getClassResolver(), buffer,
header);
}
/**
@@ -233,10 +250,10 @@ public class ClassDef implements Serializable {
}
}
descriptors = new ArrayList<>(fieldsInfo.size());
- for (ClassDef.FieldInfo fieldInfo : fieldsInfo) {
+ for (FieldInfo fieldInfo : fieldsInfo) {
Descriptor descriptor =
descriptorsMap.get(fieldInfo.getDefinedClass() + "." +
fieldInfo.getFieldName());
- Descriptor newDesc = fieldInfo.toDescriptor(resolver);
+ Descriptor newDesc = fieldInfo.toDescriptor(resolver, descriptor);
Class<?> rawType = newDesc.getRawType();
FieldType fieldType = fieldInfo.getFieldType();
if (fieldType instanceof RegisteredFieldType) {
@@ -295,11 +312,13 @@ public class ClassDef implements Serializable {
return fieldName;
}
- public boolean hasTypeTag() {
+ /** Returns whether field is annotated by an unsigned int id. */
+ public boolean hasTag() {
return false;
}
- public short getTypeTag() {
+ /** Returns annotated tag id for the field. */
+ public short getTag() {
return -1;
}
@@ -313,8 +332,9 @@ public class ClassDef implements Serializable {
* null. Don't invoke this method if class does have
<code>fieldName</code> field. In such case,
* reflection should be used to get the descriptor.
*/
- Descriptor toDescriptor(ClassResolver classResolver) {
- TypeRef<?> typeRef = fieldType.toTypeToken(classResolver);
+ Descriptor toDescriptor(ClassResolver classResolver, Descriptor
descriptor) {
+ TypeRef<?> declared = descriptor != null ? descriptor.getTypeRef() :
null;
+ TypeRef<?> typeRef = fieldType.toTypeToken(classResolver, declared);
// This field doesn't exist in peer class, so any legal modifier will be
OK.
int stubModifiers = ReflectionUtils.getField(getClass(),
"fieldName").getModifiers();
return new Descriptor(typeRef, fieldName, stubModifiers, definedClass);
@@ -355,14 +375,18 @@ public class ClassDef implements Serializable {
}
public abstract static class FieldType implements Serializable {
- public FieldType(boolean isMonomorphic, boolean trackingRef) {
+ protected final int xtypeId;
+ protected final boolean isMonomorphic;
+ protected final boolean nullable;
+ protected final boolean trackingRef;
+
+ public FieldType(int xtypeId, boolean isMonomorphic, boolean nullable,
boolean trackingRef) {
this.isMonomorphic = isMonomorphic;
this.trackingRef = trackingRef;
+ this.nullable = nullable;
+ this.xtypeId = xtypeId;
}
- protected final boolean isMonomorphic;
- protected final boolean trackingRef;
-
public boolean isMonomorphic() {
return isMonomorphic;
}
@@ -371,6 +395,10 @@ public class ClassDef implements Serializable {
return trackingRef;
}
+ public boolean nullable() {
+ return nullable;
+ }
+
/**
* Convert a serializable field type to type token. If field type is a
generic type with
* generics, the generics will be built up recursively. The final leaf
object type will be built
@@ -378,7 +406,7 @@ public class ClassDef implements Serializable {
*
* @see FinalObjectTypeStub
*/
- public abstract TypeRef<?> toTypeToken(ClassResolver classResolver);
+ public abstract TypeRef<?> toTypeToken(ClassResolver classResolver,
TypeRef<?> declared);
@Override
public boolean equals(Object o) {
@@ -399,28 +427,28 @@ public class ClassDef implements Serializable {
byte header = (byte) ((isMonomorphic ? 1 : 0) << 1);
// header of nested generic fields in collection/map will be written
independently
header |= (byte) (trackingRef ? 1 : 0);
- if (this instanceof ClassDef.RegisteredFieldType) {
- short classId = ((ClassDef.RegisteredFieldType) this).getClassId();
+ if (this instanceof RegisteredFieldType) {
+ short classId = ((RegisteredFieldType) this).getClassId();
buffer.writeVarUint32Small7(writeHeader ? ((5 + classId) << 2) |
header : 5 + classId);
- } else if (this instanceof ClassDef.EnumFieldType) {
+ } else if (this instanceof EnumFieldType) {
buffer.writeVarUint32Small7(writeHeader ? ((4) << 2) | header : 4);
- } else if (this instanceof ClassDef.ArrayFieldType) {
- ClassDef.ArrayFieldType arrayFieldType = (ClassDef.ArrayFieldType)
this;
+ } else if (this instanceof ArrayFieldType) {
+ ArrayFieldType arrayFieldType = (ArrayFieldType) this;
buffer.writeVarUint32Small7(writeHeader ? ((3) << 2) | header : 3);
buffer.writeVarUint32Small7(arrayFieldType.getDimensions());
(arrayFieldType).getComponentType().write(buffer);
- } else if (this instanceof ClassDef.CollectionFieldType) {
+ } else if (this instanceof CollectionFieldType) {
buffer.writeVarUint32Small7(writeHeader ? ((2) << 2) | header : 2);
// TODO remove it when new collection deserialization jit finished.
- ((ClassDef.CollectionFieldType) this).getElementType().write(buffer);
- } else if (this instanceof ClassDef.MapFieldType) {
+ ((CollectionFieldType) this).getElementType().write(buffer);
+ } else if (this instanceof MapFieldType) {
buffer.writeVarUint32Small7(writeHeader ? ((1) << 2) | header : 1);
// TODO remove it when new map deserialization jit finished.
- ClassDef.MapFieldType mapFieldType = (ClassDef.MapFieldType) this;
+ MapFieldType mapFieldType = (MapFieldType) this;
mapFieldType.getKeyType().write(buffer);
mapFieldType.getValueType().write(buffer);
} else {
- Preconditions.checkArgument(this instanceof ClassDef.ObjectFieldType);
+ Preconditions.checkArgument(this instanceof ObjectFieldType);
buffer.writeVarUint32Small7(writeHeader ? header : 0);
}
}
@@ -429,29 +457,113 @@ public class ClassDef implements Serializable {
write(buffer, true);
}
- public static FieldType read(MemoryBuffer buffer) {
+ public static FieldType read(MemoryBuffer buffer, TypeResolver resolver) {
int header = buffer.readVarUint32Small7();
boolean isMonomorphic = (header & 0b10) != 0;
boolean trackingRef = (header & 0b1) != 0;
- return read(buffer, isMonomorphic, trackingRef, header >>> 2);
+ return read(buffer, resolver, isMonomorphic, trackingRef, header >>> 2);
}
/** Read field type info. */
public static FieldType read(
- MemoryBuffer buffer, boolean isFinal, boolean trackingRef, int typeId)
{
+ MemoryBuffer buffer,
+ TypeResolver resolver,
+ boolean isFinal,
+ boolean trackingRef,
+ int typeId) {
if (typeId == 0) {
- return new ObjectFieldType(isFinal, trackingRef);
+ return new ObjectFieldType(-1, isFinal, true, trackingRef);
} else if (typeId == 1) {
- return new MapFieldType(isFinal, trackingRef, read(buffer),
read(buffer));
+ return new MapFieldType(
+ -1, isFinal, true, trackingRef, read(buffer, resolver),
read(buffer, resolver));
} else if (typeId == 2) {
- return new CollectionFieldType(isFinal, trackingRef, read(buffer));
+ return new CollectionFieldType(-1, isFinal, true, trackingRef,
read(buffer, resolver));
} else if (typeId == 3) {
int dims = buffer.readVarUint32Small7();
- return new ArrayFieldType(isFinal, trackingRef, read(buffer), dims);
+ return new ArrayFieldType(isFinal, trackingRef, read(buffer,
resolver), dims);
} else if (typeId == 4) {
- return EnumFieldType.getInstance();
+ return new EnumFieldType(true, -1);
} else {
- return new RegisteredFieldType(isFinal, trackingRef, (short) (typeId -
5));
+ boolean nullable = ((ClassResolver) resolver).isPrimitive((short)
typeId);
+ return new RegisteredFieldType(isFinal, nullable, trackingRef, (typeId
- 5));
+ }
+ }
+
+ public final void xwrite(MemoryBuffer buffer, boolean writeFlags) {
+ int xtypeId = this.xtypeId;
+ if (writeFlags) {
+ xtypeId = (xtypeId << 2);
+ if (nullable) {
+ xtypeId |= 0b10;
+ }
+ if (trackingRef) {
+ xtypeId |= 0b1;
+ }
+ }
+ buffer.writeVarUint32Small7(xtypeId);
+ switch (xtypeId) {
+ case Types.LIST:
+ ((CollectionFieldType) this).getElementType().xwrite(buffer, true);
+ break;
+ case Types.MAP:
+ MapFieldType mapFieldType = (MapFieldType) this;
+ mapFieldType.getKeyType().xwrite(buffer, true);
+ mapFieldType.getValueType().xwrite(buffer, true);
+ break;
+ default:
+ {
+ }
+ }
+ }
+
+ public static FieldType xread(MemoryBuffer buffer, XtypeResolver resolver)
{
+ int xtypeId = buffer.readVarUint32Small7();
+ boolean trackingRef = (xtypeId & 0b1) != 0;
+ boolean nullable = (xtypeId & 0b10) != 0;
+ xtypeId = xtypeId >>> 2;
+ return xread(buffer, resolver, xtypeId, nullable, trackingRef);
+ }
+
+ public static FieldType xread(
+ MemoryBuffer buffer,
+ XtypeResolver resolver,
+ int xtypeId,
+ boolean nullable,
+ boolean trackingRef) {
+ switch (xtypeId) {
+ case Types.LIST:
+ case Types.SET:
+ return new CollectionFieldType(
+ xtypeId, true, nullable, trackingRef, xread(buffer, resolver));
+ case Types.MAP:
+ return new MapFieldType(
+ xtypeId,
+ true,
+ nullable,
+ trackingRef,
+ xread(buffer, resolver),
+ xread(buffer, resolver));
+ case Types.ENUM:
+ case Types.NAMED_ENUM:
+ return new EnumFieldType(nullable, xtypeId);
+ case Types.UNKNOWN:
+ return new ObjectFieldType(xtypeId, false, nullable, trackingRef);
+ default:
+ {
+ if (!Types.isUserDefinedType((byte) xtypeId)) {
+ ClassInfo classInfo = resolver.getXtypeInfo(xtypeId);
+ Preconditions.checkNotNull(classInfo);
+ Class<?> cls = classInfo.getCls();
+ if (Types.isPrimitiveArray(xtypeId)) {
+ FieldType type = buildFieldType(resolver,
resolver.buildGenericType(cls));
+ return new ArrayFieldType(xtypeId, true, nullable,
trackingRef, type, 1);
+ }
+ return new RegisteredFieldType(
+ resolver.isMonomorphic(cls), nullable, trackingRef, xtypeId);
+ } else {
+ return new ObjectFieldType(xtypeId, false, nullable,
trackingRef);
+ }
+ }
}
}
}
@@ -460,9 +572,10 @@ public class ClassDef implements Serializable {
public static class RegisteredFieldType extends FieldType {
private final short classId;
- public RegisteredFieldType(boolean isFinal, boolean trackingRef, short
classId) {
- super(isFinal, trackingRef);
- this.classId = classId;
+ public RegisteredFieldType(
+ boolean isFinal, boolean nullable, boolean trackingRef, int classId) {
+ super(classId, isFinal, nullable, trackingRef);
+ this.classId = (short) classId;
}
public short getClassId() {
@@ -470,13 +583,13 @@ public class ClassDef implements Serializable {
}
@Override
- public TypeRef<?> toTypeToken(ClassResolver classResolver) {
+ public TypeRef<?> toTypeToken(ClassResolver classResolver, TypeRef<?>
declared) {
Class<?> cls = classResolver.getRegisteredClass(classId);
if (cls == null) {
LOG.warn("Class {} not registered, take it as Struct type for
deserialization.", classId);
cls = NonexistentClass.NonexistentMetaShared.class;
}
- return TypeRef.of(cls, new TypeExtMeta(trackingRef));
+ return TypeRef.of(cls, new TypeExtMeta(nullable, trackingRef));
}
@Override
@@ -523,8 +636,13 @@ public class ClassDef implements Serializable {
public static class CollectionFieldType extends FieldType {
private final FieldType elementType;
- public CollectionFieldType(boolean isFinal, boolean trackingRef, FieldType
elementType) {
- super(isFinal, trackingRef);
+ public CollectionFieldType(
+ int xtypeId,
+ boolean isFinal,
+ boolean nullable,
+ boolean trackingRef,
+ FieldType elementType) {
+ super(xtypeId, isFinal, nullable, trackingRef);
this.elementType = elementType;
}
@@ -533,9 +651,35 @@ public class ClassDef implements Serializable {
}
@Override
- public TypeRef<?> toTypeToken(ClassResolver classResolver) {
+ public TypeRef<?> toTypeToken(ClassResolver classResolver, TypeRef<?>
declared) {
// TODO support preserve element TypeExtMeta
- return collectionOf(elementType.toTypeToken(classResolver), new
TypeExtMeta(trackingRef));
+ TypeRef<? extends Collection<?>> collectionTypeRef =
+ collectionOf(
+ elementType.toTypeToken(classResolver, declared),
+ new TypeExtMeta(nullable, trackingRef));
+ if (declared == null) {
+ return collectionTypeRef;
+ }
+ Class<?> declaredClass = declared.getRawType();
+ if (!declaredClass.isArray()) {
+ return collectionTypeRef;
+ }
+ Tuple2<Class<?>, Integer> info =
TypeUtils.getArrayComponentInfo(declaredClass);
+ List<TypeRef<?>> typeRefs = new ArrayList<>(info.f1 + 1);
+ typeRefs.add(collectionTypeRef);
+ for (int i = 0; i < info.f1; i++) {
+ typeRefs.add(TypeUtils.getElementType(typeRefs.get(i)));
+ }
+ Collections.reverse(typeRefs);
+ for (int i = 1; i < typeRefs.size(); i++) {
+ TypeRef<?> arrayType = typeRefs.get(i - 1);
+ TypeRef<?> typeRef =
+ TypeRef.of(
+ Array.newInstance(arrayType.getRawType(), 1).getClass(),
+ typeRefs.get(i).getExtInfo());
+ typeRefs.set(i, typeRef);
+ }
+ return typeRefs.get(typeRefs.size() - 1);
}
@Override
@@ -584,8 +728,13 @@ public class ClassDef implements Serializable {
private final FieldType valueType;
public MapFieldType(
- boolean isFinal, boolean trackingRef, FieldType keyType, FieldType
valueType) {
- super(isFinal, trackingRef);
+ int xtypeId,
+ boolean isFinal,
+ boolean nullable,
+ boolean trackingRef,
+ FieldType keyType,
+ FieldType valueType) {
+ super(xtypeId, isFinal, nullable, trackingRef);
this.keyType = keyType;
this.valueType = valueType;
}
@@ -599,12 +748,12 @@ public class ClassDef implements Serializable {
}
@Override
- public TypeRef<?> toTypeToken(ClassResolver classResolver) {
+ public TypeRef<?> toTypeToken(ClassResolver classResolver, TypeRef<?>
declared) {
// TODO support preserve element TypeExtMeta, it will be lost when
building other TypeRef
return mapOf(
- keyType.toTypeToken(classResolver),
- valueType.toTypeToken(classResolver),
- new TypeExtMeta(trackingRef));
+ keyType.toTypeToken(classResolver, declared),
+ valueType.toTypeToken(classResolver, declared),
+ new TypeExtMeta(nullable, trackingRef));
}
@Override
@@ -643,20 +792,14 @@ public class ClassDef implements Serializable {
}
public static class EnumFieldType extends FieldType {
- private static final EnumFieldType INSTANCE = new EnumFieldType();
-
- private EnumFieldType() {
- super(true, false);
+ private EnumFieldType(boolean nullable, int xtypeId) {
+ super(xtypeId, true, nullable, false);
}
@Override
- public TypeRef<?> toTypeToken(ClassResolver classResolver) {
+ public TypeRef<?> toTypeToken(ClassResolver classResolver, TypeRef<?>
declared) {
return TypeRef.of(NonexistentClass.NonexistentEnum.class);
}
-
- public static EnumFieldType getInstance() {
- return INSTANCE;
- }
}
public static class ArrayFieldType extends FieldType {
@@ -665,14 +808,24 @@ public class ClassDef implements Serializable {
public ArrayFieldType(
boolean isMonomorphic, boolean trackingRef, FieldType componentType,
int dimensions) {
- super(isMonomorphic, trackingRef);
+ this(-1, isMonomorphic, true, trackingRef, componentType, dimensions);
+ }
+
+ public ArrayFieldType(
+ int xtypeId,
+ boolean isMonomorphic,
+ boolean nullable,
+ boolean trackingRef,
+ FieldType componentType,
+ int dimensions) {
+ super(xtypeId, isMonomorphic, nullable, trackingRef);
this.componentType = componentType;
this.dimensions = dimensions;
}
@Override
- public TypeRef<?> toTypeToken(ClassResolver classResolver) {
- TypeRef<?> componentTypeRef = componentType.toTypeToken(classResolver);
+ public TypeRef<?> toTypeToken(ClassResolver classResolver, TypeRef<?>
declared) {
+ TypeRef<?> componentTypeRef = componentType.toTypeToken(classResolver,
declared);
Class<?> componentRawType = componentTypeRef.getRawType();
if (NonexistentClass.class.isAssignableFrom(componentRawType)) {
return TypeRef.of(
@@ -680,11 +833,11 @@ public class ClassDef implements Serializable {
// here.
NonexistentClass.getNonexistentClass(
componentType instanceof EnumFieldType, dimensions, true),
- new TypeExtMeta(trackingRef));
+ new TypeExtMeta(nullable, trackingRef));
} else {
return TypeRef.of(
Array.newInstance(componentRawType, new
int[dimensions]).getClass(),
- new TypeExtMeta(trackingRef));
+ new TypeExtMeta(nullable, trackingRef));
}
}
@@ -734,15 +887,15 @@ public class ClassDef implements Serializable {
/** Class for field type which isn't registered and not collection/map type
too. */
public static class ObjectFieldType extends FieldType {
- public ObjectFieldType(boolean isFinal, boolean trackingRef) {
- super(isFinal, trackingRef);
+ public ObjectFieldType(int xtypeId, boolean isFinal, boolean nullable,
boolean trackingRef) {
+ super(xtypeId, isFinal, nullable, trackingRef);
}
@Override
- public TypeRef<?> toTypeToken(ClassResolver classResolver) {
+ public TypeRef<?> toTypeToken(ClassResolver classResolver, TypeRef<?>
declared) {
return isMonomorphic()
- ? TypeRef.of(FinalObjectTypeStub.class, new TypeExtMeta(trackingRef))
- : TypeRef.of(Object.class, new TypeExtMeta(trackingRef));
+ ? TypeRef.of(FinalObjectTypeStub.class, new TypeExtMeta(nullable,
trackingRef))
+ : TypeRef.of(Object.class, new TypeExtMeta(nullable, trackingRef));
}
@Override
@@ -757,58 +910,89 @@ public class ClassDef implements Serializable {
}
/** Build field type from generics, nested generics will be extracted too. */
- static FieldType buildFieldType(ClassResolver classResolver, Field field) {
+ static FieldType buildFieldType(TypeResolver resolver, Field field) {
Preconditions.checkNotNull(field);
- GenericType genericType = GenericType.build(field.getGenericType());
- return buildFieldType(classResolver, genericType);
+ GenericType genericType =
resolver.buildGenericType(field.getGenericType());
+ return buildFieldType(resolver, genericType);
}
/** Build field type from generics, nested generics will be extracted too. */
- private static FieldType buildFieldType(ClassResolver classResolver,
GenericType genericType) {
+ private static FieldType buildFieldType(TypeResolver resolver, GenericType
genericType) {
Preconditions.checkNotNull(genericType);
Class<?> rawType = genericType.getCls();
+ boolean isXlang = resolver.getFury().isCrossLanguage();
+ int xtypeId = -1;
+ if (isXlang) {
+ ClassInfo info = resolver.getClassInfo(genericType.getCls(), false);
+ if (info != null) {
+ xtypeId = info.getXtypeId();
+ } else {
+ xtypeId = Types.UNKNOWN;
+ }
+ }
boolean isMonomorphic = genericType.isMonomorphic();
- boolean trackingRef = genericType.trackingRef(classResolver);
+ boolean trackingRef = genericType.trackingRef(resolver);
+ // TODO support @Nullable/FuryField annotation
+ boolean nullable = !genericType.getCls().isPrimitive();
if (COLLECTION_TYPE.isSupertypeOf(genericType.getTypeRef())) {
return new CollectionFieldType(
+ xtypeId,
isMonomorphic,
+ nullable,
trackingRef,
buildFieldType(
- classResolver,
+ resolver,
genericType.getTypeParameter0() == null
? GenericType.build(Object.class)
: genericType.getTypeParameter0()));
} else if (MAP_TYPE.isSupertypeOf(genericType.getTypeRef())) {
return new MapFieldType(
+ xtypeId,
isMonomorphic,
+ nullable,
trackingRef,
buildFieldType(
- classResolver,
+ resolver,
genericType.getTypeParameter0() == null
? GenericType.build(Object.class)
: genericType.getTypeParameter0()),
buildFieldType(
- classResolver,
+ resolver,
genericType.getTypeParameter1() == null
? GenericType.build(Object.class)
: genericType.getTypeParameter1()));
} else {
- Short classId = classResolver.getRegisteredClassId(rawType);
- if (classId != null && classId != ClassResolver.NO_CLASS_ID) {
- return new RegisteredFieldType(isMonomorphic, trackingRef, classId);
+ if (isXlang
+ && !Types.isUserDefinedType((byte) xtypeId)
+ && resolver.isRegisteredById(rawType)) {
+ return new RegisteredFieldType(isMonomorphic, nullable, trackingRef,
xtypeId);
+ } else if (!isXlang && resolver.isRegisteredById(rawType)) {
+ Short classId = ((ClassResolver)
resolver).getRegisteredClassId(rawType);
+ return new RegisteredFieldType(isMonomorphic, nullable, trackingRef,
classId);
} else {
if (rawType.isEnum()) {
- return EnumFieldType.getInstance();
+ return new EnumFieldType(nullable, xtypeId);
}
if (rawType.isArray()) {
+ Class<?> elemType = rawType.getComponentType();
+ if (isXlang && !elemType.isPrimitive()) {
+ return new CollectionFieldType(
+ xtypeId,
+ isMonomorphic,
+ nullable,
+ trackingRef,
+ buildFieldType(resolver, GenericType.build(elemType)));
+ }
Tuple2<Class<?>, Integer> info =
TypeUtils.getArrayComponentInfo(rawType);
return new ArrayFieldType(
+ xtypeId,
isMonomorphic,
+ nullable,
trackingRef,
- buildFieldType(classResolver, GenericType.build(info.f0)),
+ buildFieldType(resolver, GenericType.build(info.f0)),
info.f1);
}
- return new ObjectFieldType(isMonomorphic, trackingRef);
+ return new ObjectFieldType(xtypeId, isMonomorphic, nullable,
trackingRef);
}
}
}
@@ -818,19 +1002,21 @@ public class ClassDef implements Serializable {
}
public static ClassDef buildClassDef(Fury fury, Class<?> cls, boolean
resolveParent) {
+ if (fury.isCrossLanguage()) {
+ return TypeDefEncoder.buildTypeDef(fury, cls);
+ }
return ClassDefEncoder.buildClassDef(
fury.getClassResolver(), cls, buildFields(fury, cls, resolveParent),
true);
}
/** Build class definition from fields of class. */
- public static ClassDef buildClassDef(
- ClassResolver classResolver, Class<?> type, List<Field> fields) {
+ static ClassDef buildClassDef(ClassResolver classResolver, Class<?> type,
List<Field> fields) {
return buildClassDef(classResolver, type, fields, true);
}
public static ClassDef buildClassDef(
- ClassResolver classResolver, Class<?> type, List<Field> fields, boolean
isObjectType) {
- return ClassDefEncoder.buildClassDef(classResolver, type, fields,
isObjectType);
+ ClassResolver classResolver, Class<?> type, List<Field> fields, boolean
hasFieldsMeta) {
+ return ClassDefEncoder.buildClassDef(classResolver, type, fields,
hasFieldsMeta);
}
public ClassDef replaceRootClassTo(ClassResolver classResolver, Class<?>
targetCls) {
@@ -847,6 +1033,6 @@ public class ClassDef implements Serializable {
})
.collect(Collectors.toList());
return ClassDefEncoder.buildClassDefWithFieldInfos(
- classResolver, targetCls, fieldInfos, isObjectType);
+ classResolver, targetCls, fieldInfos, hasFieldsMeta);
}
}
diff --git
a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefDecoder.java
b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefDecoder.java
index 4e693f35..f9c326df 100644
--- a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefDecoder.java
+++ b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefDecoder.java
@@ -19,19 +19,23 @@
package org.apache.fury.meta;
-import static org.apache.fury.meta.ClassDef.COMPRESSION_FLAG;
-import static org.apache.fury.meta.ClassDef.SIZE_TWO_BYTES_FLAG;
+import static org.apache.fury.meta.ClassDef.COMPRESS_META_FLAG;
+import static org.apache.fury.meta.ClassDef.HAS_FIELDS_META_FLAG;
+import static org.apache.fury.meta.ClassDef.META_SIZE_MASKS;
import static org.apache.fury.meta.ClassDefEncoder.BIG_NAME_THRESHOLD;
+import static org.apache.fury.meta.ClassDefEncoder.NUM_CLASS_THRESHOLD;
import static org.apache.fury.meta.Encoders.fieldNameEncodings;
import static org.apache.fury.meta.Encoders.pkgEncodings;
import static org.apache.fury.meta.Encoders.typeNameEncodings;
import java.util.ArrayList;
import java.util.List;
+import org.apache.fury.collection.Tuple2;
import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.meta.ClassDef.FieldType;
import org.apache.fury.meta.MetaString.Encoding;
import org.apache.fury.resolver.ClassResolver;
+import org.apache.fury.resolver.TypeResolver;
import org.apache.fury.serializer.NonexistentClass;
import org.apache.fury.util.Preconditions;
@@ -41,32 +45,30 @@ import org.apache.fury.util.Preconditions;
*
href="https://fury.apache.org/docs/specification/fury_java_serialization_spec">...</a>
*/
class ClassDefDecoder {
- public static ClassDef decodeClassDef(ClassResolver classResolver,
MemoryBuffer buffer, long id) {
- boolean sizeTwoBytes = (id & SIZE_TWO_BYTES_FLAG) != 0;
- MemoryBuffer encoded = MemoryBuffer.newHeapBuffer(32);
+ static Tuple2<byte[], byte[]> decodeClassDefBuf(
+ MemoryBuffer inputBuffer, TypeResolver resolver, long id) {
+ MemoryBuffer encoded = MemoryBuffer.newHeapBuffer(64);
encoded.writeInt64(id);
- int size;
- if (sizeTwoBytes) {
- size = buffer.readInt16() & 0xffff;
- encoded.writeInt16((short) size);
- } else {
- size = buffer.readByte() & 0xff;
- encoded.writeByte(size);
+ int size = (int) (id & META_SIZE_MASKS);
+ if (size == META_SIZE_MASKS) {
+ int moreSize = inputBuffer.readVarUint32Small14();
+ encoded.writeVarUint32(moreSize);
+ size += moreSize;
}
- byte[] encodedClassDef = buffer.readBytes(size);
+ byte[] encodedClassDef = inputBuffer.readBytes(size);
encoded.writeBytes(encodedClassDef);
- if ((id & COMPRESSION_FLAG) != 0) {
+ if ((id & COMPRESS_META_FLAG) != 0) {
encodedClassDef =
- classResolver
- .getFury()
- .getConfig()
- .getMetaCompressor()
- .decompress(encodedClassDef, 0, size);
+
resolver.getFury().getConfig().getMetaCompressor().decompress(encodedClassDef,
0, size);
}
- MemoryBuffer classDefBuf = MemoryBuffer.fromByteArray(encodedClassDef);
- long header = id & 0xff;
- int numClasses = (int) (header & 0b1111);
- if (numClasses == 0b1111) {
+ return Tuple2.of(encodedClassDef, encoded.getBytes(0,
encoded.writerIndex()));
+ }
+
+ public static ClassDef decodeClassDef(ClassResolver resolver, MemoryBuffer
buffer, long id) {
+ Tuple2<byte[], byte[]> decoded = decodeClassDefBuf(buffer, resolver, id);
+ MemoryBuffer classDefBuf = MemoryBuffer.fromByteArray(decoded.f0);
+ int numClasses = classDefBuf.readByte();
+ if (numClasses == NUM_CLASS_THRESHOLD) {
numClasses += classDefBuf.readVarUint32Small7();
}
numClasses += 1;
@@ -81,11 +83,11 @@ class ClassDefDecoder {
int numFields = currentClassHeader >>> 1;
if (isRegistered) {
short registeredId = (short) classDefBuf.readVarUint32Small7();
- if (classResolver.getRegisteredClass(registeredId) == null) {
+ if (resolver.getRegisteredClass(registeredId) == null) {
classSpec = new
ClassSpec(NonexistentClass.NonexistentMetaShared.class);
className = classSpec.entireClassName;
} else {
- Class<?> cls = classResolver.getClassInfo(registeredId).getCls();
+ Class<?> cls = resolver.getClassInfo(registeredId).getCls();
className = cls.getName();
classSpec = new ClassSpec(cls);
}
@@ -94,23 +96,23 @@ class ClassDefDecoder {
String typeName = readTypeName(classDefBuf);
classSpec = Encoders.decodePkgAndClass(pkg, typeName);
className = classSpec.entireClassName;
- if (classResolver.isRegisteredByName(className)) {
- Class<?> cls = classResolver.getRegisteredClass(className);
+ if (resolver.isRegisteredByName(className)) {
+ Class<?> cls = resolver.getRegisteredClass(className);
className = cls.getName();
classSpec = new ClassSpec(cls);
}
}
- List<ClassDef.FieldInfo> fieldInfos = readFieldsInfo(classDefBuf,
className, numFields);
+ List<ClassDef.FieldInfo> fieldInfos =
+ readFieldsInfo(classDefBuf, resolver, className, numFields);
classFields.addAll(fieldInfos);
}
Preconditions.checkNotNull(classSpec);
- boolean isObjectType = (header & ClassDef.OBJECT_TYPE_FLAG) != 0;
- return new ClassDef(
- classSpec, classFields, isObjectType, id, encoded.getBytes(0,
encoded.writerIndex()));
+ boolean hasFieldsMeta = (id & HAS_FIELDS_META_FLAG) != 0;
+ return new ClassDef(classSpec, classFields, hasFieldsMeta, id, decoded.f1);
}
private static List<ClassDef.FieldInfo> readFieldsInfo(
- MemoryBuffer buffer, String className, int numFields) {
+ MemoryBuffer buffer, ClassResolver resolver, String className, int
numFields) {
List<ClassDef.FieldInfo> fieldInfos = new ArrayList<>(numFields);
for (int i = 0; i < numFields; i++) {
int header = buffer.readByte() & 0xff;
@@ -131,13 +133,13 @@ class ClassDefDecoder {
boolean isMonomorphic = (header & 0b100) != 0;
boolean trackingRef = (header & 0b001) != 0;
int typeId = buffer.readVarUint32Small14();
- FieldType fieldType = FieldType.read(buffer, isMonomorphic, trackingRef,
typeId);
+ FieldType fieldType = FieldType.read(buffer, resolver, isMonomorphic,
trackingRef, typeId);
fieldInfos.add(new ClassDef.FieldInfo(className, fieldName, fieldType));
}
return fieldInfos;
}
- private static String readPkgName(MemoryBuffer buffer) {
+ static String readPkgName(MemoryBuffer buffer) {
// - Package name encoding(omitted when class is registered):
// - encoding algorithm:
`UTF8/ALL_TO_LOWER_SPECIAL/LOWER_UPPER_DIGIT_SPECIAL`
// - Header: `6 bits size | 2 bits encoding flags`.
@@ -147,7 +149,7 @@ class ClassDefDecoder {
return readName(Encoders.PACKAGE_DECODER, buffer, pkgEncodings);
}
- private static String readTypeName(MemoryBuffer buffer) {
+ static String readTypeName(MemoryBuffer buffer) {
// - Class name encoding(omitted when class is registered):
// - encoding algorithm:
//
`UTF8/LOWER_UPPER_DIGIT_SPECIAL/FIRST_TO_LOWER_SPECIAL/ALL_TO_LOWER_SPECIAL`
diff --git
a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefEncoder.java
b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefEncoder.java
index 9478adc3..e0f43759 100644
--- a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefEncoder.java
+++ b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefEncoder.java
@@ -19,14 +19,13 @@
package org.apache.fury.meta;
-import static org.apache.fury.meta.ClassDef.COMPRESSION_FLAG;
-import static org.apache.fury.meta.ClassDef.OBJECT_TYPE_FLAG;
-import static org.apache.fury.meta.ClassDef.SCHEMA_COMPATIBLE_FLAG;
-import static org.apache.fury.meta.ClassDef.SIZE_TWO_BYTES_FLAG;
+import static org.apache.fury.meta.ClassDef.COMPRESS_META_FLAG;
+import static org.apache.fury.meta.ClassDef.HAS_FIELDS_META_FLAG;
+import static org.apache.fury.meta.ClassDef.META_SIZE_MASKS;
+import static org.apache.fury.meta.ClassDef.NUM_HASH_BITS;
import static org.apache.fury.meta.Encoders.fieldNameEncodingsList;
import static org.apache.fury.meta.Encoders.pkgEncodingsList;
import static org.apache.fury.meta.Encoders.typeNameEncodingsList;
-import static org.apache.fury.util.MathUtils.toInt;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -43,6 +42,7 @@ import org.apache.fury.meta.ClassDef.FieldInfo;
import org.apache.fury.meta.ClassDef.FieldType;
import org.apache.fury.reflect.ReflectionUtils;
import org.apache.fury.resolver.ClassResolver;
+import org.apache.fury.resolver.TypeResolver;
import org.apache.fury.type.DescriptorGrouper;
import org.apache.fury.util.MurmurHash3;
@@ -52,6 +52,9 @@ import org.apache.fury.util.MurmurHash3;
*
href="https://fury.apache.org/docs/specification/fury_java_serialization_spec">...</a>
*/
class ClassDefEncoder {
+ // a flag to mark a type is not struct.
+ static final int NUM_CLASS_THRESHOLD = 0b1111;
+
static List<Field> buildFields(Fury fury, Class<?> cls, boolean
resolveParent) {
DescriptorGrouper descriptorGrouper =
fury.getClassResolver()
@@ -83,7 +86,7 @@ class ClassDefEncoder {
return buildFieldsInfo(resolver, buildFields(resolver.getFury(), cls,
true));
}
- static List<FieldInfo> buildFieldsInfo(ClassResolver resolver, List<Field>
fields) {
+ static List<FieldInfo> buildFieldsInfo(TypeResolver resolver, List<Field>
fields) {
List<FieldInfo> fieldInfos = new ArrayList<>();
for (Field field : fields) {
FieldInfo fieldInfo =
@@ -98,25 +101,25 @@ class ClassDefEncoder {
/** Build class definition from fields of class. */
static ClassDef buildClassDef(
- ClassResolver classResolver, Class<?> type, List<Field> fields, boolean
isObjectType) {
+ ClassResolver classResolver, Class<?> type, List<Field> fields, boolean
hasFieldsMeta) {
return buildClassDefWithFieldInfos(
- classResolver, type, buildFieldsInfo(classResolver, fields),
isObjectType);
+ classResolver, type, buildFieldsInfo(classResolver, fields),
hasFieldsMeta);
}
static ClassDef buildClassDefWithFieldInfos(
ClassResolver classResolver,
Class<?> type,
List<ClassDef.FieldInfo> fieldInfos,
- boolean isObjectType) {
+ boolean hasFieldsMeta) {
Map<String, List<FieldInfo>> classLayers = getClassFields(type,
fieldInfos);
fieldInfos = new ArrayList<>(fieldInfos.size());
classLayers.values().forEach(fieldInfos::addAll);
- MemoryBuffer encodeClassDef = encodeClassDef(classResolver, type,
classLayers, isObjectType);
+ MemoryBuffer encodeClassDef = encodeClassDef(classResolver, type,
classLayers, hasFieldsMeta);
byte[] classDefBytes = encodeClassDef.getBytes(0,
encodeClassDef.writerIndex());
return new ClassDef(
Encoders.buildClassSpec(type),
fieldInfos,
- isObjectType,
+ hasFieldsMeta,
encodeClassDef.getInt64(0),
classDefBytes);
}
@@ -127,8 +130,15 @@ class ClassDefEncoder {
ClassResolver classResolver,
Class<?> type,
Map<String, List<FieldInfo>> classLayers,
- boolean isObjectType) {
+ boolean hasFieldsMeta) {
MemoryBuffer classDefBuf = MemoryBuffer.newHeapBuffer(128);
+ int numClasses = classLayers.size() - 1; // num class must be greater than 0
+ if (numClasses >= NUM_CLASS_THRESHOLD) {
+ classDefBuf.writeByte(NUM_CLASS_THRESHOLD);
+ classDefBuf.writeVarUint32Small7(numClasses - NUM_CLASS_THRESHOLD);
+ } else {
+ classDefBuf.writeByte(numClasses);
+ }
for (Map.Entry<String, List<FieldInfo>> entry : classLayers.entrySet()) {
String className = entry.getKey();
Class<?> currentType = getType(type, className);
@@ -169,41 +179,33 @@ class ClassDefEncoder {
classDefBuf = MemoryBuffer.fromByteArray(compressed);
classDefBuf.writerIndex(compressed.length);
}
- long hash =
- MurmurHash3.murmurhash3_x64_128(
- classDefBuf.getHeapMemory(), 0, classDefBuf.writerIndex(), 47)[0];
- long header;
- int numClasses = classLayers.size() - 1; // num class must be greater than 0
- if (numClasses > 0b1110) {
- header = 0b1111;
- } else {
- header = numClasses;
- }
- header |= SCHEMA_COMPATIBLE_FLAG;
- if (isObjectType) {
- header |= OBJECT_TYPE_FLAG;
- }
+ return prependHeader(classDefBuf, isCompressed, hasFieldsMeta);
+ }
+
+ static MemoryBuffer prependHeader(
+ MemoryBuffer buffer, boolean isCompressed, boolean hasFieldsMeta) {
+ int metaSize = buffer.writerIndex();
+ long hash = MurmurHash3.murmurhash3_x64_128(buffer.getHeapMemory(), 0,
metaSize, 47)[0];
+ hash <<= (64 - NUM_HASH_BITS);
+ // this id will be part of generated codec, a negative number won't be
allowed in class name.
+ long header = Math.abs(hash);
if (isCompressed) {
- header |= COMPRESSION_FLAG;
+ header |= COMPRESS_META_FLAG;
}
- // this id will be part of generated codec, a negative number won't be
allowed in class name.
- hash <<= 8;
- header |= Math.abs(hash);
- MemoryBuffer buffer = MemoryUtils.buffer(classDefBuf.writerIndex() + 10);
- int len = classDefBuf.writerIndex() + toInt(numClasses > 0b1110);
- if (len > 255) {
- header |= SIZE_TWO_BYTES_FLAG;
- buffer.writeInt64(header);
- buffer.writeInt16((short) len);
- } else {
- buffer.writeInt64(header);
- buffer.writeByte(len);
+ if (hasFieldsMeta) {
+ header |= HAS_FIELDS_META_FLAG;
+ }
+ if (metaSize > META_SIZE_MASKS) {
+ header |= META_SIZE_MASKS;
}
- if (numClasses > 0b1110) {
- buffer.writeVarUint32Small7(numClasses - 0b1110);
+ header |= metaSize;
+ MemoryBuffer result = MemoryUtils.buffer(metaSize + 8);
+ result.writeInt64(header);
+ if (metaSize > META_SIZE_MASKS) {
+ result.writeVarUint32(metaSize - META_SIZE_MASKS);
}
- buffer.writeBytes(classDefBuf.getHeapMemory(), 0,
classDefBuf.writerIndex());
- return buffer;
+ result.writeBytes(buffer.getHeapMemory(), 0, metaSize);
+ return result;
}
private static Class<?> getType(Class<?> cls, String type) {
@@ -259,8 +261,8 @@ class ClassDefEncoder {
int encodingFlags =
fieldNameEncodingsList.indexOf(metaString.getEncoding());
byte[] encoded = metaString.getBytes();
int size = (encoded.length - 1);
- if (fieldInfo.hasTypeTag()) {
- size = fieldInfo.getTypeTag();
+ if (fieldInfo.hasTag()) {
+ size = fieldInfo.getTag();
encodingFlags = 3;
}
header |= (byte) (encodingFlags << 3);
@@ -273,14 +275,14 @@ class ClassDefEncoder {
header |= (size << 5);
buffer.writeByte(header);
}
- if (!fieldInfo.hasTypeTag()) {
+ if (!fieldInfo.hasTag()) {
buffer.writeBytes(encoded);
}
fieldType.write(buffer, false);
}
}
- private static void writePkgName(MemoryBuffer buffer, String pkg) {
+ static void writePkgName(MemoryBuffer buffer, String pkg) {
// - Package name encoding(omitted when class is registered):
// - encoding algorithm:
`UTF8/ALL_TO_LOWER_SPECIAL/LOWER_UPPER_DIGIT_SPECIAL`
// - Header: `6 bits size | 2 bits encoding flags`.
@@ -292,7 +294,7 @@ class ClassDefEncoder {
writeName(buffer, encoded,
pkgEncodingsList.indexOf(pkgMetaString.getEncoding()));
}
- private static void writeTypeName(MemoryBuffer buffer, String typeName) {
+ static void writeTypeName(MemoryBuffer buffer, String typeName) {
// - Class name encoding(omitted when class is registered):
// - encoding algorithm:
//
`UTF8/LOWER_UPPER_DIGIT_SPECIAL/FIRST_TO_LOWER_SPECIAL/ALL_TO_LOWER_SPECIAL`
diff --git
a/java/fury-core/src/main/java/org/apache/fury/meta/TypeDefDecoder.java
b/java/fury-core/src/main/java/org/apache/fury/meta/TypeDefDecoder.java
new file mode 100644
index 00000000..5df46ee9
--- /dev/null
+++ b/java/fury-core/src/main/java/org/apache/fury/meta/TypeDefDecoder.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fury.meta;
+
+import static org.apache.fury.meta.ClassDef.HAS_FIELDS_META_FLAG;
+import static org.apache.fury.meta.ClassDefDecoder.decodeClassDefBuf;
+import static org.apache.fury.meta.ClassDefDecoder.readPkgName;
+import static org.apache.fury.meta.ClassDefDecoder.readTypeName;
+import static org.apache.fury.meta.Encoders.fieldNameEncodings;
+import static org.apache.fury.meta.TypeDefEncoder.FIELD_NAME_SIZE_THRESHOLD;
+import static org.apache.fury.meta.TypeDefEncoder.REGISTER_BY_NAME_FLAG;
+import static org.apache.fury.meta.TypeDefEncoder.SMALL_NUM_FIELDS_THRESHOLD;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.fury.collection.Tuple2;
+import org.apache.fury.memory.MemoryBuffer;
+import org.apache.fury.meta.ClassDef.FieldType;
+import org.apache.fury.meta.MetaString.Encoding;
+import org.apache.fury.resolver.ClassInfo;
+import org.apache.fury.resolver.XtypeResolver;
+import org.apache.fury.serializer.NonexistentClass;
+
+/**
+ * A decoder which decode binary into {@link ClassDef}. See spec documentation:
+ * docs/specification/fury_xlang_serialization_spec.md <a
+ *
href="https://fury.apache.org/docs/specification/fury_xlang_serialization_spec">...</a>
+ */
+class TypeDefDecoder {
+ public static ClassDef decodeClassDef(XtypeResolver resolver, MemoryBuffer
inputBuffer, long id) {
+ Tuple2<byte[], byte[]> decoded = decodeClassDefBuf(inputBuffer, resolver,
id);
+ MemoryBuffer buffer = MemoryBuffer.fromByteArray(decoded.f0);
+ byte header = buffer.readByte();
+ int numFields = header & SMALL_NUM_FIELDS_THRESHOLD;
+ if (numFields == SMALL_NUM_FIELDS_THRESHOLD) {
+ numFields += buffer.readVarUint32Small7() + SMALL_NUM_FIELDS_THRESHOLD;
+ }
+ ClassSpec classSpec;
+ if ((header & REGISTER_BY_NAME_FLAG) != 0) {
+ String namespace = readPkgName(buffer);
+ String typeName = readTypeName(buffer);
+ ClassInfo userTypeInfo = resolver.getUserTypeInfo(namespace, typeName);
+ if (userTypeInfo == null) {
+ classSpec = new
ClassSpec(NonexistentClass.NonexistentMetaShared.class);
+ } else {
+ classSpec = new ClassSpec(userTypeInfo.getCls());
+ }
+ } else {
+ int xtypeId = buffer.readVarUint32Small7();
+ ClassInfo userTypeInfo = resolver.getUserTypeInfo(xtypeId);
+ if (userTypeInfo == null) {
+ classSpec = new
ClassSpec(NonexistentClass.NonexistentMetaShared.class);
+ } else {
+ classSpec = new ClassSpec(userTypeInfo.getCls());
+ }
+ }
+ List<ClassDef.FieldInfo> classFields =
+ readFieldsInfo(buffer, resolver, classSpec.entireClassName, numFields);
+ boolean hasFieldsMeta = (id & HAS_FIELDS_META_FLAG) != 0;
+ return new ClassDef(classSpec, classFields, hasFieldsMeta, id, decoded.f1);
+ }
+
+ // | header + type info + field name | ... | header + type info + field name
|
+ private static List<ClassDef.FieldInfo> readFieldsInfo(
+ MemoryBuffer buffer, XtypeResolver resolver, String className, int
numFields) {
+ List<ClassDef.FieldInfo> fieldInfos = new ArrayList<>(numFields);
+ for (int i = 0; i < numFields; i++) {
+ // header: 2 bits field name encoding + 4 bits size + nullability flag +
ref tracking flag
+ byte header = buffer.readByte();
+ int encodingFlags = (header >>> 6) & 0b11;
+ boolean useTagID = encodingFlags == 3;
+ int fieldNameSize = (header >>> 2) & 0b1111;
+ if (fieldNameSize == FIELD_NAME_SIZE_THRESHOLD) {
+ fieldNameSize += buffer.readVarUint32Small7();
+ }
+ fieldNameSize += 1;
+ boolean nullable = (header & 0b10) != 0;
+ boolean trackingRef = (header & 0b1) != 0;
+ int typeId = buffer.readVarUint32Small14();
+ FieldType fieldType = FieldType.xread(buffer, resolver, typeId,
nullable, trackingRef);
+ // read field name
+ if (useTagID) {
+ throw new UnsupportedOperationException(
+ "Type tag not supported currently, parsed fieldInfos %s " +
fieldInfos);
+ }
+ Encoding encoding = fieldNameEncodings[encodingFlags];
+ String fieldName =
+ Encoders.FIELD_NAME_DECODER.decode(buffer.readBytes(fieldNameSize),
encoding);
+ fieldInfos.add(new ClassDef.FieldInfo(className, fieldName, fieldType));
+ }
+ return fieldInfos;
+ }
+}
diff --git
a/java/fury-core/src/main/java/org/apache/fury/meta/TypeDefEncoder.java
b/java/fury-core/src/main/java/org/apache/fury/meta/TypeDefEncoder.java
new file mode 100644
index 00000000..d50c1452
--- /dev/null
+++ b/java/fury-core/src/main/java/org/apache/fury/meta/TypeDefEncoder.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fury.meta;
+
+import static org.apache.fury.meta.ClassDefEncoder.prependHeader;
+import static org.apache.fury.meta.ClassDefEncoder.writePkgName;
+import static org.apache.fury.meta.ClassDefEncoder.writeTypeName;
+import static org.apache.fury.meta.Encoders.fieldNameEncodingsList;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.fury.Fury;
+import org.apache.fury.memory.MemoryBuffer;
+import org.apache.fury.meta.ClassDef.FieldInfo;
+import org.apache.fury.meta.ClassDef.FieldType;
+import org.apache.fury.reflect.ReflectionUtils;
+import org.apache.fury.resolver.ClassInfo;
+import org.apache.fury.resolver.TypeResolver;
+import org.apache.fury.resolver.XtypeResolver;
+import org.apache.fury.type.Descriptor;
+import org.apache.fury.type.DescriptorGrouper;
+import org.apache.fury.type.Types;
+import org.apache.fury.util.Preconditions;
+
+/**
+ * An encoder which encode {@link ClassDef} into binary. See spec
documentation:
+ * docs/specification/fury_xlang_serialization_spec.md <a
+ *
href="https://fury.apache.org/docs/specification/fury_xlang_serialization_spec">...</a>
+ */
+class TypeDefEncoder {
+ /** Build class definition from fields of class. */
+ static ClassDef buildTypeDef(Fury fury, Class<?> type) {
+ DescriptorGrouper descriptorGrouper =
+ fury.getClassResolver()
+ .createDescriptorGrouper(
+ fury.getClassResolver().getAllDescriptorsMap(type,
true).values(),
+ false,
+ Function.identity());
+ List<Field> fields =
+ descriptorGrouper.getSortedDescriptors().stream()
+ .map(Descriptor::getField)
+ .collect(Collectors.toList());
+ return buildClassDefWithFieldInfos(
+ fury.getXtypeResolver(), type,
buildFieldsInfo(fury.getXtypeResolver(), type, fields));
+ }
+
+ static List<FieldInfo> buildFieldsInfo(TypeResolver resolver, Class<?> type,
List<Field> fields) {
+ return fields.stream()
+ .map(
+ field ->
+ new FieldInfo(
+ type.getName(), field.getName(),
ClassDef.buildFieldType(resolver, field)))
+ .collect(Collectors.toList());
+ }
+
+ static ClassDef buildClassDefWithFieldInfos(
+ XtypeResolver resolver, Class<?> type, List<FieldInfo> fieldInfos) {
+ fieldInfos = new ArrayList<>(getClassFields(type, fieldInfos).values());
+ MemoryBuffer encodeClassDef = encodeClassDef(resolver, type, fieldInfos);
+ byte[] classDefBytes = encodeClassDef.getBytes(0,
encodeClassDef.writerIndex());
+ return new ClassDef(
+ Encoders.buildClassSpec(type), fieldInfos, true,
encodeClassDef.getInt64(0), classDefBytes);
+ }
+
+ static final int SMALL_NUM_FIELDS_THRESHOLD = 0b11111;
+ static final int REGISTER_BY_NAME_FLAG = 0b100000;
+ static final int FIELD_NAME_SIZE_THRESHOLD = 0b1111;
+
+ // see spec documentation: docs/specification/xlang_serialization_spec.md
+ // https://fury.apache.org/docs/specification/fury_xlang_serialization_spec
+ static MemoryBuffer encodeClassDef(
+ XtypeResolver resolver, Class<?> type, List<FieldInfo> fields) {
+ ClassInfo classInfo = resolver.getClassInfo(type);
+ Preconditions.checkArgument(
+ Types.isStructType(classInfo.getXtypeId()), "%s is not a struct",
type);
+ MemoryBuffer buffer = MemoryBuffer.newHeapBuffer(128);
+ buffer.writeByte(-1); // placeholder for header, update later
+ int currentClassHeader = fields.size();
+ if (fields.size() >= SMALL_NUM_FIELDS_THRESHOLD) {
+ currentClassHeader = SMALL_NUM_FIELDS_THRESHOLD;
+ buffer.writeVarUint32(fields.size() - SMALL_NUM_FIELDS_THRESHOLD);
+ }
+ if (resolver.isRegisteredById(type)) {
+ buffer.writeVarUint32(classInfo.getXtypeId());
+ } else {
+ Preconditions.checkArgument(resolver.isRegisteredByName(type));
+ currentClassHeader |= REGISTER_BY_NAME_FLAG;
+ String ns = classInfo.decodeNamespace();
+ String typename = classInfo.decodeTypeName();
+ writePkgName(buffer, ns);
+ writeTypeName(buffer, typename);
+ }
+ buffer.putByte(0, currentClassHeader);
+ writeFieldsInfo(resolver, buffer, fields);
+
+ byte[] compressed =
+ resolver
+ .getFury()
+ .getMetaCompressor()
+ .compress(buffer.getHeapMemory(), 0, buffer.writerIndex());
+ boolean isCompressed = false;
+ if (compressed.length < buffer.writerIndex()) {
+ isCompressed = true;
+ buffer = MemoryBuffer.fromByteArray(compressed);
+ buffer.writerIndex(compressed.length);
+ }
+ return prependHeader(buffer, isCompressed, true);
+ }
+
+ static Map<String, FieldInfo> getClassFields(Class<?> type, List<FieldInfo>
fieldsInfo) {
+ Map<String, FieldInfo> sortedClassFields = new LinkedHashMap<>();
+ Map<String, List<FieldInfo>> classFields =
ClassDefEncoder.groupClassFields(fieldsInfo);
+ for (Class<?> clz : ReflectionUtils.getAllClasses(type, true)) {
+ List<FieldInfo> fieldInfos = classFields.get(clz.getName());
+ if (fieldInfos != null) {
+ for (FieldInfo fieldInfo : fieldInfos) {
+ sortedClassFields.put(fieldInfo.getFieldName(), fieldInfo);
+ }
+ }
+ }
+ return sortedClassFields;
+ }
+
+ /** Write field type and name info. Every field info format: `header + type
info + field name` */
+ static void writeFieldsInfo(XtypeResolver resolver, MemoryBuffer buffer,
List<FieldInfo> fields) {
+ for (FieldInfo fieldInfo : fields) {
+ FieldType fieldType = fieldInfo.getFieldType();
+ // header: 2 bits field name encoding + 4 bits size + nullability flag +
ref tracking flag
+ int header = ((fieldType.trackingRef() ? 1 : 0));
+ header |= fieldType.nullable() ? 0b10 : 0b00;
+ int size, encodingFlags;
+ byte[] encoded = null;
+ if (fieldInfo.hasTag()) {
+ size = fieldInfo.getTag();
+ encodingFlags = 3;
+ } else {
+ MetaString metaString =
Encoders.encodeFieldName(fieldInfo.getFieldName());
+ // Encoding
`UTF8/ALL_TO_LOWER_SPECIAL/LOWER_UPPER_DIGIT_SPECIAL/TAG_ID`
+ encodingFlags =
fieldNameEncodingsList.indexOf(metaString.getEncoding());
+ encoded = metaString.getBytes();
+ size = (encoded.length - 1);
+ }
+ header |= (byte) (encodingFlags << 6);
+ boolean bigSize = size >= FIELD_NAME_SIZE_THRESHOLD;
+ if (bigSize) {
+ header |= 0b00111100;
+ buffer.writeByte(header);
+ buffer.writeVarUint32Small7(size - FIELD_NAME_SIZE_THRESHOLD);
+ } else {
+ header |= (size << 2);
+ buffer.writeByte(header);
+ }
+ fieldType.xwrite(buffer, false);
+ // write field name
+ if (!fieldInfo.hasTag()) {
+ buffer.writeBytes(encoded);
+ }
+ }
+ }
+}
diff --git a/java/fury-core/src/main/java/org/apache/fury/meta/TypeExtMeta.java
b/java/fury-core/src/main/java/org/apache/fury/meta/TypeExtMeta.java
index 37df8273..e5c53388 100644
--- a/java/fury-core/src/main/java/org/apache/fury/meta/TypeExtMeta.java
+++ b/java/fury-core/src/main/java/org/apache/fury/meta/TypeExtMeta.java
@@ -20,18 +20,24 @@
package org.apache.fury.meta;
public class TypeExtMeta {
+ private final boolean nullable;
private final boolean trackingRef;
- TypeExtMeta(boolean trackingRef) {
+ TypeExtMeta(boolean nullable, boolean trackingRef) {
+ this.nullable = nullable;
this.trackingRef = trackingRef;
}
+ public boolean nullable() {
+ return nullable;
+ }
+
public boolean trackingRef() {
return trackingRef;
}
@Override
public String toString() {
- return "TypeExtMeta{" + "trackingRef=" + trackingRef + '}';
+ return "TypeExtMeta{" + "nullable=" + nullable + ", trackingRef=" +
trackingRef + '}';
}
}
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 8b38aec5..930a086b 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
@@ -20,7 +20,6 @@
package org.apache.fury.resolver;
import static org.apache.fury.Fury.NOT_SUPPORT_XLANG;
-import static org.apache.fury.meta.ClassDef.SIZE_TWO_BYTES_FLAG;
import static org.apache.fury.meta.Encoders.GENERIC_ENCODER;
import static org.apache.fury.meta.Encoders.PACKAGE_DECODER;
import static org.apache.fury.meta.Encoders.PACKAGE_ENCODER;
@@ -219,7 +218,7 @@ public class ClassResolver implements TypeResolver {
// use a lower load factor to minimize hash collision
private static final float furyMapLoadFactor = 0.25f;
private static final int estimatedNumRegistered = 150;
- private static final String SET_META__CONTEXT_MSG =
+ static final String SET_META__CONTEXT_MSG =
"Meta context must be set before serialization, "
+ "please set meta context by SerializationContext.setMetaContext";
static final ClassInfo NIL_CLASS_INFO =
@@ -552,6 +551,7 @@ public class ClassResolver implements TypeResolver {
return extRegistry.registeredClasses.containsKey(name);
}
+ @Override
public boolean isRegisteredByName(Class<?> cls) {
return extRegistry.registeredClasses.inverse().containsKey(cls);
}
@@ -570,6 +570,7 @@ public class ClassResolver implements TypeResolver {
}
}
+ @Override
public boolean isRegisteredById(Class<?> cls) {
return extRegistry.registeredClassIdMap.get(cls) != null;
}
@@ -616,6 +617,7 @@ public class ClassResolver implements TypeResolver {
* a class is registered but not an inner class with inner serializer, it
will still be taken as
* non-final to write class def, so that it can be deserialized by the peer
still.
*/
+ @Override
public boolean isMonomorphic(Class<?> clz) {
if (fury.isCrossLanguage()) {
if (TypeUtils.unwrap(clz).isPrimitive() || clz.isEnum() || clz ==
String.class) {
@@ -1520,7 +1522,7 @@ public class ClassResolver implements TypeResolver {
return classInfo;
}
- private ClassInfo readClassInfoWithMetaShare(MetaContext metaContext, int
index) {
+ ClassInfo readClassInfoWithMetaShare(MetaContext metaContext, int index) {
ClassDef classDef = metaContext.readClassDefs.get(index);
Tuple2<ClassDef, ClassInfo> classDefTuple =
extRegistry.classIdToDef.get(classDef.getId());
ClassInfo classInfo;
@@ -1561,7 +1563,7 @@ public class ClassResolver implements TypeResolver {
classDef = classDefTuple.f0;
}
Class<?> cls = loadClass(classDef.getClassSpec());
- if (!classDef.isObjectType()) {
+ if (!classDef.hasFieldsMeta()) {
classInfo = getClassInfo(cls);
} else {
classInfo = getMetaSharedClassInfo(classDef, cls);
@@ -1671,11 +1673,7 @@ public class ClassResolver implements TypeResolver {
long id = buffer.readInt64();
Tuple2<ClassDef, ClassInfo> tuple2 = extRegistry.classIdToDef.get(id);
if (tuple2 != null) {
- int size =
- (id & SIZE_TWO_BYTES_FLAG) == 0
- ? buffer.readByte() & 0xff
- : buffer.readInt16() & 0xffff;
- buffer.increaseReaderIndex(size);
+ ClassDef.skipClassDef(buffer, id);
} else {
tuple2 = readClassDef(buffer, id);
}
@@ -1685,7 +1683,7 @@ public class ClassResolver implements TypeResolver {
}
private Tuple2<ClassDef, ClassInfo> readClassDef(MemoryBuffer buffer, long
header) {
- ClassDef readClassDef = ClassDef.readClassDef(this, buffer, header);
+ ClassDef readClassDef = ClassDef.readClassDef(fury, buffer, header);
Tuple2<ClassDef, ClassInfo> tuple2 =
extRegistry.classIdToDef.get(readClassDef.getId());
if (tuple2 == null) {
tuple2 = putClassDef(readClassDef, null);
@@ -2121,6 +2119,7 @@ public class ClassResolver implements TypeResolver {
}
}
+ @Override
public Fury getFury() {
return fury;
}
diff --git
a/java/fury-core/src/main/java/org/apache/fury/resolver/TypeResolver.java
b/java/fury-core/src/main/java/org/apache/fury/resolver/TypeResolver.java
index 9554314d..93eb8927 100644
--- a/java/fury-core/src/main/java/org/apache/fury/resolver/TypeResolver.java
+++ b/java/fury-core/src/main/java/org/apache/fury/resolver/TypeResolver.java
@@ -20,6 +20,7 @@
package org.apache.fury.resolver;
import java.lang.reflect.Type;
+import org.apache.fury.Fury;
import org.apache.fury.annotation.Internal;
import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.reflect.TypeRef;
@@ -34,6 +35,12 @@ public interface TypeResolver {
boolean isRegistered(Class<?> cls);
+ boolean isRegisteredById(Class<?> cls);
+
+ boolean isRegisteredByName(Class<?> cls);
+
+ boolean isMonomorphic(Class<?> clz);
+
ClassInfo getClassInfo(Class<?> cls);
ClassInfo getClassInfo(Class<?> cls, boolean createIfAbsent);
@@ -57,4 +64,6 @@ public interface TypeResolver {
GenericType buildGenericType(Type type);
void initialize();
+
+ Fury getFury();
}
diff --git
a/java/fury-core/src/main/java/org/apache/fury/resolver/XtypeResolver.java
b/java/fury-core/src/main/java/org/apache/fury/resolver/XtypeResolver.java
index 5acaa3c1..b5fa0007 100644
--- a/java/fury-core/src/main/java/org/apache/fury/resolver/XtypeResolver.java
+++ b/java/fury-core/src/main/java/org/apache/fury/resolver/XtypeResolver.java
@@ -43,12 +43,14 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.fury.Fury;
import org.apache.fury.collection.IdentityMap;
+import org.apache.fury.collection.IdentityObjectIntMap;
import org.apache.fury.collection.LongMap;
import org.apache.fury.collection.ObjectMap;
import org.apache.fury.config.Config;
@@ -58,6 +60,7 @@ import org.apache.fury.logging.Logger;
import org.apache.fury.logging.LoggerFactory;
import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.memory.Platform;
+import org.apache.fury.meta.ClassDef;
import org.apache.fury.meta.Encoders;
import org.apache.fury.meta.MetaString;
import org.apache.fury.reflect.ReflectionUtils;
@@ -103,6 +106,8 @@ public class XtypeResolver implements TypeResolver {
new ObjectMap<>(16, loadFactor);
private final ObjectMap<String, ClassInfo> qualifiedType2ClassInfo =
new ObjectMap<>(16, loadFactor);
+ private final Map<Class<?>, ClassDef> classDefMap = new HashMap<>();
+ private final boolean shareMeta;
private int xtypeIdGenerator = 64;
// Use ClassInfo[] or LongMap?
@@ -116,6 +121,7 @@ public class XtypeResolver implements TypeResolver {
this.fury = fury;
this.classResolver = fury.getClassResolver();
classResolver.xtypeResolver = this;
+ shareMeta = fury.getConfig().isMetaShareEnabled();
this.generics = fury.getGenerics();
this.metaStringResolver = fury.getMetaStringResolver();
}
@@ -138,6 +144,10 @@ public class XtypeResolver implements TypeResolver {
// We can relax this limit in the future.
Preconditions.checkArgument(typeId < MAX_TYPE_ID, "Too big type id %s",
typeId);
ClassInfo classInfo = classInfoMap.get(type);
+ if (type.isArray()) {
+ buildClassInfo(type);
+ return;
+ }
Serializer<?> serializer = null;
if (classInfo != null) {
serializer = classInfo.serializer;
@@ -285,6 +295,53 @@ public class XtypeResolver implements TypeResolver {
return classInfoMap.get(cls) != null;
}
+ @Override
+ public boolean isRegisteredById(Class<?> cls) {
+ ClassInfo classInfo = classInfoMap.get(cls);
+ if (classInfo == null) {
+ return false;
+ }
+ byte xtypeId = (byte) classInfo.xtypeId;
+ if (xtypeId <= 0) {
+ return false;
+ }
+ switch (xtypeId) {
+ case Types.NAMED_COMPATIBLE_STRUCT:
+ case Types.NAMED_ENUM:
+ case Types.NAMED_STRUCT:
+ case Types.NAMED_EXT:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ @Override
+ public boolean isRegisteredByName(Class<?> cls) {
+ ClassInfo classInfo = classInfoMap.get(cls);
+ if (classInfo == null) {
+ return false;
+ }
+ byte xtypeId = (byte) classInfo.xtypeId;
+ if (xtypeId <= 0) {
+ return false;
+ }
+ switch (xtypeId) {
+ case Types.NAMED_COMPATIBLE_STRUCT:
+ case Types.NAMED_ENUM:
+ case Types.NAMED_STRUCT:
+ case Types.NAMED_EXT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isMonomorphic(Class<?> clz) {
+ return classResolver.isMonomorphic(clz);
+ }
+
@Override
public ClassInfo getClassInfo(Class<?> cls) {
ClassInfo classInfo = classInfoMap.get(cls);
@@ -315,9 +372,27 @@ public class XtypeResolver implements TypeResolver {
return classInfo;
}
+ public ClassInfo getXtypeInfo(int typeId) {
+ return xtypeIdToClassMap.get(typeId);
+ }
+
+ public ClassInfo getUserTypeInfo(String namespace, String typeName) {
+ String name = qualifiedName(namespace, typeName);
+ return qualifiedType2ClassInfo.get(name);
+ }
+
+ public ClassInfo getUserTypeInfo(int userTypeId) {
+ Preconditions.checkArgument((byte) (userTypeId) < Types.UNKNOWN);
+ return xtypeIdToClassMap.get(userTypeId);
+ }
+
@Override
public boolean needToWriteRef(TypeRef<?> typeRef) {
- return getClassInfo(typeRef.getRawType()).serializer.needToWriteRef();
+ ClassInfo classInfo = classInfoMap.get(typeRef.getRawType());
+ if (classInfo == null) {
+ return fury.trackingRef();
+ }
+ return classInfo.serializer.needToWriteRef();
}
@Override
@@ -457,6 +532,10 @@ public class XtypeResolver implements TypeResolver {
case Types.NAMED_STRUCT:
case Types.NAMED_COMPATIBLE_STRUCT:
case Types.NAMED_EXT:
+ if (shareMeta) {
+ writeSharedClassMeta(buffer, classInfo);
+ return;
+ }
assert classInfo.namespaceBytes != null;
metaStringResolver.writeMetaStringBytes(buffer,
classInfo.namespaceBytes);
assert classInfo.typeNameBytes != null;
@@ -467,6 +546,31 @@ public class XtypeResolver implements TypeResolver {
}
}
+ public void writeSharedClassMeta(MemoryBuffer buffer, ClassInfo classInfo) {
+ MetaContext metaContext = fury.getSerializationContext().getMetaContext();
+ assert metaContext != null : ClassResolver.SET_META__CONTEXT_MSG;
+ IdentityObjectIntMap<Class<?>> classMap = metaContext.classMap;
+ int newId = classMap.size;
+ int id = classMap.putOrGet(classInfo.cls, newId);
+ if (id >= 0) {
+ buffer.writeVarUint32(id);
+ } else {
+ buffer.writeVarUint32(newId);
+ ClassDef classDef = classInfo.classDef;
+ if (classDef == null) {
+ classDef = buildClassDef(classInfo);
+ }
+ metaContext.writingClassDefs.add(classDef);
+ }
+ }
+
+ private ClassDef buildClassDef(ClassInfo classInfo) {
+ ClassDef classDef =
+ classDefMap.computeIfAbsent(classInfo.cls, cls ->
ClassDef.buildClassDef(fury, cls));
+ classInfo.classDef = classDef;
+ return classDef;
+ }
+
@Override
public <T> Serializer<T> getSerializer(Class<T> cls) {
return (Serializer) getClassInfo(cls).serializer;
@@ -501,6 +605,9 @@ public class XtypeResolver implements TypeResolver {
case Types.NAMED_STRUCT:
case Types.NAMED_COMPATIBLE_STRUCT:
case Types.NAMED_EXT:
+ if (shareMeta) {
+ return readSharedClassMeta(buffer);
+ }
MetaStringBytes packageBytes =
metaStringResolver.readMetaStringBytes(buffer);
MetaStringBytes simpleClassNameBytes =
metaStringResolver.readMetaStringBytes(buffer);
return loadBytesToClassInfo(internalTypeId, packageBytes,
simpleClassNameBytes);
@@ -517,6 +624,17 @@ public class XtypeResolver implements TypeResolver {
}
}
+ private ClassInfo readSharedClassMeta(MemoryBuffer buffer) {
+ MetaContext metaContext = fury.getSerializationContext().getMetaContext();
+ assert metaContext != null : ClassResolver.SET_META__CONTEXT_MSG;
+ int id = buffer.readVarUint32Small14();
+ ClassInfo classInfo = metaContext.readClassInfos.get(id);
+ if (classInfo == null) {
+ classInfo = classResolver.readClassInfoWithMetaShare(metaContext, id);
+ }
+ return classInfo;
+ }
+
private void throwUnexpectTypeIdException(long xtypeId) {
throw new IllegalStateException(String.format("Type id %s not registered",
xtypeId));
}
@@ -617,4 +735,9 @@ public class XtypeResolver implements TypeResolver {
private boolean isEnum(int internalTypeId) {
return internalTypeId == Types.ENUM || internalTypeId == Types.NAMED_ENUM;
}
+
+ @Override
+ public Fury getFury() {
+ return fury;
+ }
}
diff --git a/java/fury-core/src/main/java/org/apache/fury/type/Types.java
b/java/fury-core/src/main/java/org/apache/fury/type/Types.java
index a76a2414..c4b582de 100644
--- a/java/fury-core/src/main/java/org/apache/fury/type/Types.java
+++ b/java/fury-core/src/main/java/org/apache/fury/type/Types.java
@@ -162,6 +162,8 @@ public class Types {
/** An (arrow table) object. */
public static final int ARROW_TABLE = 39;
+ public static final int UNKNOWN = 63;
+
// Helper methods
public static boolean isStructType(int value) {
return value == STRUCT
@@ -178,6 +180,10 @@ public class Types {
return value == ENUM || value == NAMED_ENUM;
}
+ public static boolean isUserDefinedType(byte typeId) {
+ return isStructType(typeId) || isExtType(typeId) || isEnumType(typeId);
+ }
+
private static final Map<Class, Integer> PRIMITIVE_TYPE_ID_MAP =
ofHashMap(
boolean.class, BOOL,
@@ -192,4 +198,60 @@ public class Types {
Preconditions.checkArgument(cls.isPrimitive(), "Class %s is not
primitive", cls);
return PRIMITIVE_TYPE_ID_MAP.getOrDefault(cls, -1);
}
+
+ public static boolean isPrimitiveType(int typeId) {
+ // noinspection Duplicates
+ switch (typeId) {
+ case BOOL:
+ case INT8:
+ case INT16:
+ case INT32:
+ case INT64:
+ case FLOAT32:
+ case FLOAT64:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static boolean isPrimitiveArray(int typeId) {
+ // noinspection Duplicates
+ switch (typeId) {
+ case BOOL_ARRAY:
+ case INT8_ARRAY:
+ case INT16_ARRAY:
+ case INT32_ARRAY:
+ case INT64_ARRAY:
+ case FLOAT32_ARRAY:
+ case FLOAT64_ARRAY:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static int getPrimitiveArrayTypeId(int typeId) {
+ switch (typeId) {
+ case BOOL:
+ return BOOL_ARRAY;
+ case INT8:
+ return INT8_ARRAY;
+ case INT16:
+ return INT16_ARRAY;
+ case INT32:
+ return INT32_ARRAY;
+ case INT64:
+ return INT64_ARRAY;
+ case FLOAT16:
+ return FLOAT16_ARRAY;
+ case FLOAT32:
+ return FLOAT32_ARRAY;
+ case FLOAT64:
+ return FLOAT64_ARRAY;
+ default:
+ throw new IllegalArgumentException(
+ String.format("Type id %d is not a primitive id", typeId));
+ }
+ }
}
diff --git
a/java/fury-core/src/test/java/org/apache/fury/meta/ClassDefEncoderTest.java
b/java/fury-core/src/test/java/org/apache/fury/meta/ClassDefEncoderTest.java
index 6565afdf..e5ff89e7 100644
--- a/java/fury-core/src/test/java/org/apache/fury/meta/ClassDefEncoderTest.java
+++ b/java/fury-core/src/test/java/org/apache/fury/meta/ClassDefEncoderTest.java
@@ -44,7 +44,7 @@ public class ClassDefEncoderTest {
MemoryBuffer buffer =
ClassDefEncoder.encodeClassDef(
fury.getClassResolver(), type, getClassFields(type, fieldsInfo),
true);
- ClassDef classDef = ClassDef.readClassDef(fury.getClassResolver(), buffer);
+ ClassDef classDef = ClassDef.readClassDef(fury, buffer);
Assert.assertEquals(classDef.getClassName(), type.getName());
Assert.assertEquals(classDef.getFieldsInfo().size(),
type.getDeclaredFields().length);
Assert.assertEquals(classDef.getFieldsInfo(), fieldsInfo);
@@ -59,8 +59,7 @@ public class ClassDefEncoderTest {
Fury fury = Fury.builder().withMetaShare(true).build();
ClassDef classDef = ClassDef.buildClassDef(fury, type);
ClassDef classDef1 =
- ClassDef.readClassDef(
- fury.getClassResolver(),
MemoryBuffer.fromByteArray(classDef.getEncoded()));
+ ClassDef.readClassDef(fury,
MemoryBuffer.fromByteArray(classDef.getEncoded()));
Assert.assertEquals(classDef1, classDef);
}
}
@@ -77,8 +76,7 @@ public class ClassDefEncoderTest {
Fury fury =
Fury.builder().withLanguage(Language.JAVA).requireClassRegistration(true).build();
ClassDef classDef = ClassDef.buildClassDef(fury, Foo2.class);
ClassDef classDef1 =
- ClassDef.readClassDef(
- fury.getClassResolver(),
MemoryBuffer.fromByteArray(classDef.getEncoded()));
+ ClassDef.readClassDef(fury,
MemoryBuffer.fromByteArray(classDef.getEncoded()));
Assert.assertEquals(classDef, classDef1);
}
@@ -91,8 +89,7 @@ public class ClassDefEncoderTest {
TestClassLengthTestClassLengthTestClassLengthTestClassLengthTestClassLengthTestClassLengthTestClassLength
.InnerClassTestLengthInnerClassTestLengthInnerClassTestLength.class);
ClassDef classDef1 =
- ClassDef.readClassDef(
- fury.getClassResolver(),
MemoryBuffer.fromByteArray(classDef.getEncoded()));
+ ClassDef.readClassDef(fury,
MemoryBuffer.fromByteArray(classDef.getEncoded()));
Assert.assertEquals(classDef1, classDef);
}
diff --git
a/java/fury-core/src/test/java/org/apache/fury/meta/ClassDefTest.java
b/java/fury-core/src/test/java/org/apache/fury/meta/ClassDefTest.java
index 7ecf20b0..9e1ab43d 100644
--- a/java/fury-core/src/test/java/org/apache/fury/meta/ClassDefTest.java
+++ b/java/fury-core/src/test/java/org/apache/fury/meta/ClassDefTest.java
@@ -48,7 +48,7 @@ public class ClassDefTest extends FuryTestBase {
private long longField;
}
- private static class TestFieldsOrderClass2 extends TestFieldsOrderClass1 {
+ static class TestFieldsOrderClass2 extends TestFieldsOrderClass1 {
private int intField1;
private boolean booleanField;
private int childIntField2;
@@ -58,14 +58,14 @@ public class ClassDefTest extends FuryTestBase {
private long childLongField;
}
- private static class DuplicateFieldClass extends TestFieldsOrderClass1 {
+ static class DuplicateFieldClass extends TestFieldsOrderClass1 {
private int intField1;
private boolean booleanField;
private Object objField;
private long longField;
}
- private static class ContainerClass extends TestFieldsOrderClass1 {
+ static class ContainerClass extends TestFieldsOrderClass1 {
private int intField1;
private long longField;
private Collection<String> collection;
@@ -99,7 +99,7 @@ public class ClassDefTest extends FuryTestBase {
ImmutableList.of(TestFieldsOrderClass1.class.getDeclaredField("longField")));
MemoryBuffer buffer = MemoryBuffer.newHeapBuffer(32);
classDef.writeClassDef(buffer);
- ClassDef classDef1 = ClassDef.readClassDef(fury.getClassResolver(),
buffer);
+ ClassDef classDef1 = ClassDef.readClassDef(fury, buffer);
assertEquals(classDef1.getClassName(), classDef.getClassName());
assertEquals(classDef1, classDef);
}
@@ -115,7 +115,7 @@ public class ClassDefTest extends FuryTestBase {
ReflectionUtils.getFields(TestFieldsOrderClass1.class, true).size());
MemoryBuffer buffer = MemoryBuffer.newHeapBuffer(32);
classDef.writeClassDef(buffer);
- ClassDef classDef1 = ClassDef.readClassDef(fury.getClassResolver(),
buffer);
+ ClassDef classDef1 = ClassDef.readClassDef(fury, buffer);
assertEquals(classDef1.getClassName(), classDef.getClassName());
assertEquals(classDef1, classDef);
}
@@ -131,7 +131,7 @@ public class ClassDefTest extends FuryTestBase {
ReflectionUtils.getFields(TestFieldsOrderClass2.class, true).size());
MemoryBuffer buffer = MemoryBuffer.newHeapBuffer(32);
classDef.writeClassDef(buffer);
- ClassDef classDef1 = ClassDef.readClassDef(fury.getClassResolver(),
buffer);
+ ClassDef classDef1 = ClassDef.readClassDef(fury, buffer);
assertEquals(classDef1.getClassName(), classDef.getClassName());
assertEquals(classDef1, classDef);
}
@@ -152,7 +152,7 @@ public class ClassDefTest extends FuryTestBase {
ReflectionUtils.getFields(DuplicateFieldClass.class, true).size());
MemoryBuffer buffer = MemoryBuffer.newHeapBuffer(32);
classDef.writeClassDef(buffer);
- ClassDef classDef1 = ClassDef.readClassDef(fury.getClassResolver(),
buffer);
+ ClassDef classDef1 = ClassDef.readClassDef(fury, buffer);
assertEquals(classDef1.getClassName(), classDef.getClassName());
assertEquals(classDef1, classDef);
}
@@ -168,7 +168,7 @@ public class ClassDefTest extends FuryTestBase {
assertEquals(classDef.getFieldsInfo().size(), fields.size());
MemoryBuffer buffer = MemoryBuffer.newHeapBuffer(32);
classDef.writeClassDef(buffer);
- ClassDef classDef1 = ClassDef.readClassDef(fury.getClassResolver(),
buffer);
+ ClassDef classDef1 = ClassDef.readClassDef(fury, buffer);
assertEquals(classDef1.getClassName(), classDef.getClassName());
assertEquals(classDef1, classDef);
}
@@ -178,14 +178,14 @@ public class ClassDefTest extends FuryTestBase {
Fury fury = Fury.builder().withMetaShare(true).build();
ClassDef classDef = ClassDef.buildClassDef(fury, Map.class);
assertTrue(classDef.getFieldsInfo().isEmpty());
- assertTrue(classDef.isObjectType());
+ assertTrue(classDef.hasFieldsMeta());
}
@Test
public void testTypeExtInfo() {
Fury fury = Fury.builder().withMetaShare(true).build();
ClassResolver classResolver = fury.getClassResolver();
- assertTrue(classResolver.needToWriteRef(TypeRef.of(Foo.class, new
TypeExtMeta(true))));
- assertFalse(classResolver.needToWriteRef(TypeRef.of(Foo.class, new
TypeExtMeta(false))));
+ assertTrue(classResolver.needToWriteRef(TypeRef.of(Foo.class, new
TypeExtMeta(true, true))));
+ assertFalse(classResolver.needToWriteRef(TypeRef.of(Foo.class, new
TypeExtMeta(true, false))));
}
}
diff --git a/java/fury-core/src/test/java/org/apache/fury/meta/TypeDefTest.java
b/java/fury-core/src/test/java/org/apache/fury/meta/TypeDefTest.java
new file mode 100644
index 00000000..dee1eaaa
--- /dev/null
+++ b/java/fury-core/src/test/java/org/apache/fury/meta/TypeDefTest.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fury.meta;
+
+import static org.testng.Assert.assertEquals;
+
+import org.apache.fury.Fury;
+import org.apache.fury.FuryTestBase;
+import org.apache.fury.config.Language;
+import org.apache.fury.memory.MemoryBuffer;
+import org.apache.fury.meta.ClassDefTest.TestFieldsOrderClass1;
+import org.apache.fury.meta.ClassDefTest.TestFieldsOrderClass2;
+import org.apache.fury.reflect.ReflectionUtils;
+import org.testng.annotations.Test;
+
+public class TypeDefTest extends FuryTestBase {
+
+ @Test
+ public void testClassDefSerialization() {
+ Fury fury =
builder().withLanguage(Language.XLANG).withMetaShare(true).build();
+ fury.register(TestFieldsOrderClass1.class, "demo.Class1");
+ ClassDef classDef = ClassDef.buildClassDef(fury,
TestFieldsOrderClass1.class, true);
+ MemoryBuffer buffer = MemoryBuffer.newHeapBuffer(32);
+ classDef.writeClassDef(buffer);
+ ClassDef classDef1 = ClassDef.readClassDef(fury, buffer);
+ assertEquals(classDef1.getClassName(), classDef.getClassName());
+ assertEquals(classDef1, classDef);
+ }
+
+ @Test
+ public void testClassDefInheritanceDuplicatedFields() {
+ Fury fury =
builder().withLanguage(Language.XLANG).withMetaShare(true).build();
+ fury.register(TestFieldsOrderClass2.class, "demo.Class2");
+ ClassDef classDef = ClassDef.buildClassDef(fury,
TestFieldsOrderClass2.class);
+ assertEquals(classDef.getClassName(),
TestFieldsOrderClass2.class.getName());
+ // xtype ignore duplicate fields from parent class.
+ assertEquals(
+ classDef.getFieldsInfo().size(),
+ ReflectionUtils.getFields(TestFieldsOrderClass2.class, true).size() -
1);
+ MemoryBuffer buffer = MemoryBuffer.newHeapBuffer(32);
+ classDef.writeClassDef(buffer);
+ ClassDef classDef1 = ClassDef.readClassDef(fury, buffer);
+ assertEquals(classDef1.getClassName(), classDef.getClassName());
+ assertEquals(classDef1, classDef);
+ }
+}
diff --git
a/java/fury-extensions/src/test/java/org/apache/fury/meta/ClassDefEncoderTest.java
b/java/fury-extensions/src/test/java/org/apache/fury/meta/ClassDefEncoderTest.java
index d581e941..ce1caa9e 100644
---
a/java/fury-extensions/src/test/java/org/apache/fury/meta/ClassDefEncoderTest.java
+++
b/java/fury-extensions/src/test/java/org/apache/fury/meta/ClassDefEncoderTest.java
@@ -46,7 +46,7 @@ public class ClassDefEncoderTest {
MemoryBuffer buffer =
ClassDefEncoder.encodeClassDef(
fury.getClassResolver(), type, getClassFields(type, fieldsInfo),
true);
- ClassDef classDef = ClassDef.readClassDef(fury.getClassResolver(), buffer);
+ ClassDef classDef = ClassDef.readClassDef(fury, buffer);
Assert.assertEquals(classDef.getClassName(), type.getName());
Assert.assertEquals(classDef.getFieldsInfo().size(),
type.getDeclaredFields().length);
Assert.assertEquals(classDef.getFieldsInfo(), fieldsInfo);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]