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 cfe96802 feat(java): fury row encoder now supports implementing
interfaces with simple value type (#2250)
cfe96802 is described below
commit cfe96802b07208e620715304ec37be930450f6b7
Author: Steven Schlansker <[email protected]>
AuthorDate: Sun May 25 20:25:02 2025 -0700
feat(java): fury row encoder now supports implementing interfaces with
simple value type (#2250)
## What does this PR do?
You can now call `Encoders.registerLazyBeanInterface` and Fury will
generate a bean implementation type for you.
This should allow easy integration with Immutables library for example -
I will be testing that over the coming days.
## Related issues
Fixes #2223
## Does this PR introduce any user-facing change?
New api to register interface types. Since this was not implemented
before, there should be no breaking change.
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
---
.../java/org/apache/fury/annotation/FuryField.java | 2 +-
.../java/org/apache/fury/builder/CodecBuilder.java | 8 +-
.../apache/fury/builder/ObjectCodecBuilder.java | 2 +-
.../org/apache/fury/codegen/CodegenContext.java | 12 +-
.../main/java/org/apache/fury/meta/ClassDef.java | 5 +-
.../java/org/apache/fury/meta/ClassDefEncoder.java | 2 +-
.../java/org/apache/fury/meta/TypeDefEncoder.java | 2 +-
.../main/java/org/apache/fury/reflect/TypeRef.java | 4 +
.../org/apache/fury/resolver/ClassResolver.java | 26 +++-
.../org/apache/fury/resolver/FieldResolver.java | 18 ++-
.../apache/fury/serializer/ObjectSerializer.java | 2 +-
.../main/java/org/apache/fury/type/Descriptor.java | 121 ++++++++++++-------
.../apache/fury/type/TypeResolutionContext.java | 105 +++++++++++++++++
.../main/java/org/apache/fury/type/TypeUtils.java | 124 +++++++++----------
.../java/org/apache/fury/CrossLanguageTest.java | 2 +-
java/fury-format/README.md | 12 +-
.../fury/format/encoder/ArrayDataForEach.java | 11 +-
.../fury/format/encoder/ArrayEncoderBuilder.java | 7 +-
.../format/encoder/BaseBinaryEncoderBuilder.java | 112 +++++++++---------
.../fury/format/encoder/MapEncoderBuilder.java | 14 +--
.../fury/format/encoder/RowEncoderBuilder.java | 131 ++++++++++++++++++---
.../apache/fury/format/row/binary/BinaryUtils.java | 9 +-
.../org/apache/fury/format/type/TypeInference.java | 76 +++++-------
.../format/encoder/ImplementInterfaceTest.java | 127 ++++++++++++++++++++
24 files changed, 664 insertions(+), 270 deletions(-)
diff --git
a/java/fury-core/src/main/java/org/apache/fury/annotation/FuryField.java
b/java/fury-core/src/main/java/org/apache/fury/annotation/FuryField.java
index 00d1d250..9f3d9bdb 100644
--- a/java/fury-core/src/main/java/org/apache/fury/annotation/FuryField.java
+++ b/java/fury-core/src/main/java/org/apache/fury/annotation/FuryField.java
@@ -25,7 +25,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.FIELD)
+@Target({ElementType.FIELD, ElementType.METHOD})
public @interface FuryField {
/** Whether field is nullable, default false. */
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 d0f88062..bbda1ff0 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
@@ -67,7 +67,6 @@ import org.apache.fury.util.StringUtils;
import org.apache.fury.util.function.Functions;
import org.apache.fury.util.record.RecordComponent;
import org.apache.fury.util.record.RecordUtils;
-import sun.misc.Unsafe;
/**
* Base builder for generating code to serialize java bean in row-format or
object stream format.
@@ -93,6 +92,7 @@ public abstract class CodecBuilder {
protected final TypeRef<?> beanType;
protected final Class<?> beanClass;
protected final boolean isRecord;
+ protected final boolean isInterface;
private final Set<String> duplicatedFields;
protected Reference furyRef = new Reference(FURY_NAME,
TypeRef.of(Fury.class));
public static final Reference recordComponentDefaultValues =
@@ -105,10 +105,11 @@ public abstract class CodecBuilder {
this.beanType = beanType;
this.beanClass = getRawType(beanType);
isRecord = RecordUtils.isRecord(beanClass);
+ isInterface = beanClass.isInterface();
if (isRecord) {
recordCtrAccessible = recordCtrAccessible(beanClass);
}
- duplicatedFields =
Descriptor.getSortedDuplicatedFields(beanClass).keySet();
+ duplicatedFields =
Descriptor.getSortedDuplicatedMembers(beanClass).keySet();
// don't ctx.addImport beanClass, because it maybe causes name collide.
ctx.reserveName(FURY_NAME);
ctx.reserveName(ROOT_OBJECT_NAME);
@@ -191,6 +192,9 @@ public abstract class CodecBuilder {
TypeRef<?> fieldType = descriptor.getTypeRef();
Class<?> rawType = descriptor.getRawType();
String fieldName = descriptor.getName();
+ if (isInterface) {
+ return new Invoke(inputBeanExpr, descriptor.getName(), fieldName,
fieldType);
+ }
if (isRecord) {
return getRecordFieldValue(inputBeanExpr, descriptor);
}
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 3429512b..eb0a837f 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
@@ -96,7 +96,7 @@ public class ObjectCodecBuilder extends
BaseObjectCodecBuilder {
.getClassDef(beanClass, true)
.getDescriptors(classResolver, beanClass));
} else {
- descriptors = fury.getClassResolver().getAllDescriptorsMap(beanClass,
true).values();
+ descriptors = fury.getClassResolver().getFieldDescriptors(beanClass,
true);
}
DescriptorGrouper grouper =
classResolver.createDescriptorGrouper(descriptors, false);
descriptors = grouper.getSortedDescriptors();
diff --git
a/java/fury-core/src/main/java/org/apache/fury/codegen/CodegenContext.java
b/java/fury-core/src/main/java/org/apache/fury/codegen/CodegenContext.java
index d6568d46..203df52c 100644
--- a/java/fury-core/src/main/java/org/apache/fury/codegen/CodegenContext.java
+++ b/java/fury-core/src/main/java/org/apache/fury/codegen/CodegenContext.java
@@ -140,6 +140,7 @@ public class CodegenContext {
String pkg;
LinkedHashSet<String> imports = new LinkedHashSet<>();
String className;
+ String classModifiers = "public final";
String[] superClasses;
String[] interfaces;
List<FieldInfo> fields = new ArrayList<>();
@@ -415,6 +416,15 @@ public class CodegenContext {
this.className = className;
}
+ /**
+ * Set class modifiers. Default is {@code public final}.
+ *
+ * @param classModifiers the new class modifiers
+ */
+ public void setClassModifiers(String classModifiers) {
+ this.classModifiers = classModifiers;
+ }
+
/**
* Set super classes.
*
@@ -626,7 +636,7 @@ public class CodegenContext {
codeBuilder.append('\n');
}
- codeBuilder.append(String.format("public final class %s ", className));
+ codeBuilder.append(String.format("%s class %s ", classModifiers,
className));
if (superClasses != null) {
codeBuilder.append(String.format("extends %s ", String.join(", ",
superClasses)));
}
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 1d330e6a..a2f56b41 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
@@ -30,6 +30,7 @@ import java.io.ObjectStreamClass;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
+import java.lang.reflect.Member;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -240,9 +241,9 @@ public class ClassDef implements Serializable {
*/
public List<Descriptor> getDescriptors(ClassResolver resolver, Class<?> cls)
{
if (descriptors == null) {
- SortedMap<Field, Descriptor> allDescriptorsMap =
resolver.getAllDescriptorsMap(cls, true);
+ SortedMap<Member, Descriptor> allDescriptorsMap =
resolver.getAllDescriptorsMap(cls, true);
Map<String, Descriptor> descriptorsMap = new HashMap<>();
- for (Map.Entry<Field, Descriptor> e : allDescriptorsMap.entrySet()) {
+ for (Map.Entry<Member, Descriptor> e : allDescriptorsMap.entrySet()) {
if (descriptorsMap.put(
e.getKey().getDeclaringClass().getName() + "." +
e.getKey().getName(), e.getValue())
!= null) {
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 e0f43759..6ddf56e3 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
@@ -59,7 +59,7 @@ class ClassDefEncoder {
DescriptorGrouper descriptorGrouper =
fury.getClassResolver()
.createDescriptorGrouper(
- fury.getClassResolver().getAllDescriptorsMap(cls,
resolveParent).values(),
+ fury.getClassResolver().getFieldDescriptors(cls,
resolveParent),
false,
Function.identity());
List<Field> fields = new ArrayList<>();
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
index d50c1452..f828a206 100644
--- 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
@@ -55,7 +55,7 @@ class TypeDefEncoder {
DescriptorGrouper descriptorGrouper =
fury.getClassResolver()
.createDescriptorGrouper(
- fury.getClassResolver().getAllDescriptorsMap(type,
true).values(),
+ fury.getClassResolver().getFieldDescriptors(type, true),
false,
Function.identity());
List<Field> fields =
diff --git a/java/fury-core/src/main/java/org/apache/fury/reflect/TypeRef.java
b/java/fury-core/src/main/java/org/apache/fury/reflect/TypeRef.java
index c521096d..eb06df10 100644
--- a/java/fury-core/src/main/java/org/apache/fury/reflect/TypeRef.java
+++ b/java/fury-core/src/main/java/org/apache/fury/reflect/TypeRef.java
@@ -167,6 +167,10 @@ public class TypeRef<T> {
return type instanceof Class && ((Class<?>) type).isPrimitive();
}
+ public boolean isInterface() {
+ return getRawType().isInterface();
+ }
+
/**
* Returns true if this type is known to be an array type, such as {@code
int[]}, {@code T[]},
* {@code <? extends Map<String, Integer>[]>} etc.
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 930a086b..f4661179 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
@@ -38,6 +38,7 @@ import java.io.Externalizable;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
+import java.lang.reflect.Member;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
@@ -262,7 +263,7 @@ public class ClassResolver implements TypeResolver {
// Tuple2<Class, Class>: Tuple2<From Class, To Class>
private final Map<Tuple2<Class<?>, Class<?>>, ClassInfo>
transformedClassInfo = new HashMap<>();
// TODO(chaokunyang) Better to use soft reference, see ObjectStreamClass.
- private final ConcurrentHashMap<Tuple2<Class<?>, Boolean>,
SortedMap<Field, Descriptor>>
+ private final ConcurrentHashMap<Tuple2<Class<?>, Boolean>,
SortedMap<Member, Descriptor>>
descriptorsCache = new ConcurrentHashMap<>();
private ClassChecker classChecker = (classResolver, className) -> true;
private GenericType objectGenericType;
@@ -873,6 +874,7 @@ public class ClassResolver implements TypeResolver {
}
/** Get or create serializer for <code>cls</code>. */
+ @Override
@SuppressWarnings("unchecked")
public <T> Serializer<T> getSerializer(Class<T> cls) {
Preconditions.checkNotNull(cls);
@@ -1187,8 +1189,20 @@ public class ClassResolver implements TypeResolver {
return fieldResolver;
}
+ public List<Descriptor> getFieldDescriptors(Class<?> clz, boolean
searchParent) {
+ SortedMap<Member, Descriptor> allDescriptors = getAllDescriptorsMap(clz,
searchParent);
+ List<Descriptor> result = new ArrayList<>(allDescriptors.size());
+ allDescriptors.forEach(
+ (member, descriptor) -> {
+ if (member instanceof Field) {
+ result.add(descriptor);
+ }
+ });
+ return result;
+ }
+
// thread safe
- public SortedMap<Field, Descriptor> getAllDescriptorsMap(Class<?> clz,
boolean searchParent) {
+ public SortedMap<Member, Descriptor> getAllDescriptorsMap(Class<?> clz,
boolean searchParent) {
// when jit thread query this, it is already built by serialization main
thread.
return extRegistry.descriptorsCache.computeIfAbsent(
Tuple2.of(clz, searchParent), t ->
Descriptor.getAllDescriptorsMap(clz, searchParent));
@@ -1198,6 +1212,7 @@ public class ClassResolver implements TypeResolver {
* Whether to track reference for this type. If false, reference tracing of
subclasses may be
* ignored too.
*/
+ @Override
public boolean needToWriteRef(TypeRef<?> typeRef) {
Object extInfo = typeRef.getExtInfo();
if (extInfo instanceof TypeExtMeta) {
@@ -1239,6 +1254,7 @@ public class ClassResolver implements TypeResolver {
}
/** Get classinfo by cache, update cache if miss. */
+ @Override
public ClassInfo getClassInfo(Class<?> cls, ClassInfoHolder classInfoHolder)
{
ClassInfo classInfo = classInfoHolder.classInfo;
if (classInfo.getCls() != cls) {
@@ -1439,6 +1455,7 @@ public class ClassResolver implements TypeResolver {
// }
/** Write classname for java serialization. */
+ @Override
public void writeClassInfo(MemoryBuffer buffer, ClassInfo classInfo) {
if (metaContextShareEnabled) {
// FIXME(chaokunyang) Register class but not register serializer can't
be used with
@@ -1835,6 +1852,7 @@ public class ClassResolver implements TypeResolver {
}
/** Read class info, update classInfoHolder if cache not hit. */
+ @Override
@CodegenInvoke
public ClassInfo readClassInfo(MemoryBuffer buffer, ClassInfoHolder
classInfoHolder) {
if (metaContextShareEnabled) {
@@ -1986,6 +2004,7 @@ public class ClassResolver implements TypeResolver {
public void resetWrite() {}
+ @Override
public GenericType buildGenericType(TypeRef<?> typeRef) {
return GenericType.build(
typeRef,
@@ -1998,6 +2017,7 @@ public class ClassResolver implements TypeResolver {
});
}
+ @Override
public GenericType buildGenericType(Type type) {
GenericType genericType = extRegistry.genericTypes.get(type);
if (genericType != null) {
@@ -2030,10 +2050,12 @@ public class ClassResolver implements TypeResolver {
}
// Invoked by fury JIT.
+ @Override
public ClassInfo nilClassInfo() {
return new ClassInfo(this, null, null, NO_CLASS_ID, NOT_SUPPORT_XLANG);
}
+ @Override
public ClassInfoHolder nilClassInfoHolder() {
return new ClassInfoHolder(nilClassInfo());
}
diff --git
a/java/fury-core/src/main/java/org/apache/fury/resolver/FieldResolver.java
b/java/fury-core/src/main/java/org/apache/fury/resolver/FieldResolver.java
index 35337d5f..396bebb5 100644
--- a/java/fury-core/src/main/java/org/apache/fury/resolver/FieldResolver.java
+++ b/java/fury-core/src/main/java/org/apache/fury/resolver/FieldResolver.java
@@ -28,6 +28,7 @@ import static
org.apache.fury.resolver.FieldResolver.FieldInfoEncodingType.SEPAR
import static org.apache.fury.type.TypeUtils.getRawType;
import java.lang.reflect.Field;
+import java.lang.reflect.Member;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
@@ -42,6 +43,7 @@ import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.fury.Fury;
import org.apache.fury.annotation.FuryField;
import org.apache.fury.collection.Tuple2;
@@ -194,19 +196,29 @@ public class FieldResolver {
public static FieldResolver of(
Fury fury, Class<?> type, boolean resolveParent, boolean
ignoreCollectionType) {
// all fields of class and super classes should be a consistent order
between jvm process.
- SortedMap<Field, Descriptor> allFieldsMap =
+ SortedMap<Member, Descriptor> allFieldsMap =
fury.getClassResolver().getAllDescriptorsMap(type, resolveParent);
Set<String> duplicatedFields;
if (resolveParent) {
- duplicatedFields = Descriptor.getSortedDuplicatedFields(type).keySet();
+ duplicatedFields = Descriptor.getSortedDuplicatedMembers(type).keySet();
} else {
duplicatedFields = new HashSet<>();
}
List<ClassField> allFields =
-
allFieldsMap.keySet().stream().map(ClassField::new).collect(Collectors.toList());
+ allFieldsMap.keySet().stream()
+ .flatMap(FieldResolver::mapField)
+ .map(ClassField::new)
+ .collect(Collectors.toList());
return new FieldResolver(fury, type, ignoreCollectionType, allFields,
duplicatedFields);
}
+ private static Stream<Field> mapField(Member member) {
+ if (member instanceof Field) {
+ return Stream.of((Field) member);
+ }
+ return Stream.empty();
+ }
+
private final Class<?> cls;
private final Fury fury;
private final RefResolver refResolver;
diff --git
a/java/fury-core/src/main/java/org/apache/fury/serializer/ObjectSerializer.java
b/java/fury-core/src/main/java/org/apache/fury/serializer/ObjectSerializer.java
index 82e4cae7..3c131ca5 100644
---
a/java/fury-core/src/main/java/org/apache/fury/serializer/ObjectSerializer.java
+++
b/java/fury-core/src/main/java/org/apache/fury/serializer/ObjectSerializer.java
@@ -98,7 +98,7 @@ public final class ObjectSerializer<T> extends
AbstractObjectSerializer<T> {
ClassDef classDef = classResolver.getClassDef(cls, resolveParent);
descriptors = classDef.getDescriptors(classResolver, cls);
} else {
- descriptors = fury.getClassResolver().getAllDescriptorsMap(cls,
resolveParent).values();
+ descriptors = fury.getClassResolver().getFieldDescriptors(cls,
resolveParent);
}
DescriptorGrouper descriptorGrouper =
classResolver.createDescriptorGrouper(descriptors, false);
descriptors = descriptorGrouper.getSortedDescriptors();
diff --git a/java/fury-core/src/main/java/org/apache/fury/type/Descriptor.java
b/java/fury-core/src/main/java/org/apache/fury/type/Descriptor.java
index 05cd7a33..9d21c46d 100644
--- a/java/fury-core/src/main/java/org/apache/fury/type/Descriptor.java
+++ b/java/fury-core/src/main/java/org/apache/fury/type/Descriptor.java
@@ -24,6 +24,7 @@ import static
org.apache.fury.util.Preconditions.checkArgument;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.lang.reflect.Field;
+import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
@@ -37,6 +38,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
+import java.util.TreeSet;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -60,7 +62,8 @@ import org.apache.fury.util.record.RecordUtils;
* @see Ignore
*/
public class Descriptor {
- private static Cache<Class<?>, Tuple2<SortedMap<Field, Descriptor>,
SortedMap<Field, Descriptor>>>
+ private static Cache<
+ Class<?>, Tuple2<SortedMap<Member, Descriptor>, SortedMap<Member,
Descriptor>>>
descCache =
CacheBuilder.newBuilder().weakKeys().softValues().concurrencyLevel(64).build();
private static final Map<Class<?>, AtomicBoolean> flags =
Collections.synchronizedMap(new WeakHashMap<>());
@@ -132,6 +135,21 @@ public class Descriptor {
}
}
+ private Descriptor(Method readMethod) {
+ this.field = null;
+ this.typeName = readMethod.getReturnType().getName();
+ this.name = readMethod.getName();
+ this.modifier = readMethod.getModifiers();
+ this.declaringClass = readMethod.getDeclaringClass().getName();
+ this.readMethod = readMethod;
+ this.writeMethod = null;
+ this.typeRef = TypeRef.of(readMethod.getGenericReturnType());
+ this.furyField = readMethod.getAnnotation(FuryField.class);
+ if (!readMethod.getReturnType().isPrimitive()) {
+ this.nullable = furyField == null || furyField.nullable();
+ }
+ }
+
private Descriptor(
TypeRef<?> typeRef,
String typeName,
@@ -292,8 +310,8 @@ public class Descriptor {
*/
public static List<Descriptor> getDescriptors(Class<?> clz) {
// TODO(chaokunyang) add cache by weak class key, see
java.io.ObjectStreamClass.WeakClassKey.
- SortedMap<Field, Descriptor> allDescriptorsMap = getAllDescriptorsMap(clz);
- Map<String, List<Field>> duplicateNameFields =
getDuplicateNameFields(allDescriptorsMap);
+ SortedMap<Member, Descriptor> allDescriptorsMap =
getAllDescriptorsMap(clz);
+ Map<String, List<Member>> duplicateNameFields =
getDuplicateNames(allDescriptorsMap);
checkArgument(
duplicateNameFields.size() == 0, "%s has duplicate fields %s", clz,
duplicateNameFields);
return new ArrayList<>(allDescriptorsMap.values());
@@ -304,8 +322,8 @@ public class Descriptor {
* not allowed to have duplicate name field.
*/
public static SortedMap<String, Descriptor> getDescriptorsMap(Class<?> clz) {
- SortedMap<Field, Descriptor> allDescriptorsMap = getAllDescriptorsMap(clz);
- Map<String, List<Field>> duplicateNameFields =
getDuplicateNameFields(allDescriptorsMap);
+ SortedMap<Member, Descriptor> allDescriptorsMap =
getAllDescriptorsMap(clz);
+ Map<String, List<Member>> duplicateNameFields =
getDuplicateNames(allDescriptorsMap);
Preconditions.checkArgument(
duplicateNameFields.size() == 0, "%s has duplicate fields %s", clz,
duplicateNameFields);
TreeMap<String, Descriptor> map = new TreeMap<>();
@@ -313,14 +331,14 @@ public class Descriptor {
return map;
}
- private static final ClassValue<Map<String, List<Field>>>
sortedDuplicatedFields =
- new ClassValue<Map<String, List<Field>>>() {
+ private static final ClassValue<Map<String, List<Member>>>
sortedDuplicatedMembers =
+ new ClassValue<Map<String, List<Member>>>() {
@Override
- protected Map<String, List<Field>> computeValue(Class<?> type) {
- SortedMap<Field, Descriptor> allFields =
Descriptor.getAllDescriptorsMap(type);
- Map<String, List<Field>> duplicated =
Descriptor.getDuplicateNameFields(allFields);
- Map<String, List<Field>> map = new HashMap<>();
- for (Map.Entry<String, List<Field>> e : duplicated.entrySet()) {
+ protected Map<String, List<Member>> computeValue(Class<?> type) {
+ SortedMap<Member, Descriptor> allFields =
Descriptor.getAllDescriptorsMap(type);
+ Map<String, List<Member>> duplicated =
Descriptor.getDuplicateNames(allFields);
+ Map<String, List<Member>> map = new HashMap<>();
+ for (Map.Entry<String, List<Member>> e : duplicated.entrySet()) {
e.getValue()
.sort(
(f1, f2) -> {
@@ -340,36 +358,35 @@ public class Descriptor {
}
};
- public static Map<String, List<Field>> getDuplicateNameFields(
- SortedMap<Field, Descriptor> allDescriptorsMap) {
- Map<String, List<Field>> duplicateNameFields = new HashMap<>();
- for (Field field : allDescriptorsMap.keySet()) {
- duplicateNameFields.compute(
- field.getName(),
- (fieldName, fields) -> {
- if (fields == null) {
- fields = new ArrayList<>();
+ public static Map<String, List<Member>> getDuplicateNames(
+ SortedMap<Member, Descriptor> allDescriptorsMap) {
+ Map<String, List<Member>> duplicateNames = new HashMap<>();
+ for (Member member : allDescriptorsMap.keySet()) {
+ duplicateNames.compute(
+ member.getName(),
+ (memberName, members) -> {
+ if (members == null) {
+ members = new ArrayList<>();
}
- fields.add(field);
- return fields;
+ members.add(member);
+ return members;
});
}
- Map<String, List<Field>> map = new HashMap<>();
- for (Map.Entry<String, List<Field>> e : duplicateNameFields.entrySet()) {
+ Map<String, List<Member>> map = new HashMap<>();
+ for (Map.Entry<String, List<Member>> e : duplicateNames.entrySet()) {
if (Objects.requireNonNull(e.getValue()).size() > 1) {
map.put(e.getKey(), e.getValue());
}
}
- duplicateNameFields = map;
- return duplicateNameFields;
+ return map;
}
- public static Map<String, List<Field>> getSortedDuplicatedFields(Class<?>
cls) {
- return sortedDuplicatedFields.get(cls);
+ public static Map<String, List<Member>> getSortedDuplicatedMembers(Class<?>
cls) {
+ return sortedDuplicatedMembers.get(cls);
}
public static boolean hasDuplicateNameFields(Class<?> clz) {
- return !getSortedDuplicatedFields(clz).isEmpty();
+ return !getSortedDuplicatedMembers(clz).isEmpty();
}
/**
@@ -377,7 +394,13 @@ public class Descriptor {
* name first and declaring class second. Super class and sub class can have
same name field.
*/
public static Set<Field> getFields(Class<?> clz) {
- return getAllDescriptorsMap(clz).keySet();
+ Set<Field> fields = new TreeSet<>(memberComparator);
+ for (Member member : getAllDescriptorsMap(clz).keySet()) {
+ if (member instanceof Field) {
+ fields.add((Field) member);
+ }
+ }
+ return fields;
}
/**
@@ -385,24 +408,24 @@ public class Descriptor {
* field name first and declaring class second. Super class and subclass can
have same names
* field.
*/
- public static SortedMap<Field, Descriptor> getAllDescriptorsMap(Class<?>
clz) {
+ public static SortedMap<Member, Descriptor> getAllDescriptorsMap(Class<?>
clz) {
return getAllDescriptorsMap(clz, true);
}
- private static final Comparator<Field> fieldComparator =
- ((Field f1, Field f2) -> {
- int compare = f1.getName().compareTo(f2.getName());
+ private static final Comparator<Member> memberComparator =
+ ((Member m1, Member m2) -> {
+ int compare = m1.getName().compareTo(m2.getName());
if (compare == 0) { // class and super classes have same named field
- return
f1.getDeclaringClass().getName().compareTo(f2.getDeclaringClass().getName());
+ return
m1.getDeclaringClass().getName().compareTo(m2.getDeclaringClass().getName());
} else {
return compare;
}
});
- public static SortedMap<Field, Descriptor> getAllDescriptorsMap(
+ public static SortedMap<Member, Descriptor> getAllDescriptorsMap(
Class<?> clz, boolean searchParent) {
try {
- Tuple2<SortedMap<Field, Descriptor>, SortedMap<Field, Descriptor>>
tuple2 =
+ Tuple2<SortedMap<Member, Descriptor>, SortedMap<Member, Descriptor>>
tuple2 =
descCache.get(clz, () -> createAllDescriptorsMap(clz));
if (searchParent) {
return tuple2.f0;
@@ -414,11 +437,11 @@ public class Descriptor {
}
}
- private static Tuple2<SortedMap<Field, Descriptor>, SortedMap<Field,
Descriptor>>
+ private static Tuple2<SortedMap<Member, Descriptor>, SortedMap<Member,
Descriptor>>
createAllDescriptorsMap(Class<?> clz) {
// use TreeMap to sort to fix field order
- TreeMap<Field, Descriptor> descriptorMap = new TreeMap<>(fieldComparator);
- TreeMap<Field, Descriptor> currentDescriptorMap = new
TreeMap<>(fieldComparator);
+ TreeMap<Member, Descriptor> descriptorMap = new
TreeMap<>(memberComparator);
+ TreeMap<Member, Descriptor> currentDescriptorMap = new
TreeMap<>(memberComparator);
Class<?> clazz = clz;
// TODO(chaokunyang) use fury compiler thread pool
ExecutorService compilationService = ForkJoinPool.commonPool();
@@ -437,6 +460,16 @@ public class Descriptor {
currentDescriptorMap = new TreeMap<>(descriptorMap);
return Tuple2.of(descriptorMap, currentDescriptorMap);
}
+ if (clazz.isInterface()) {
+ for (Method method : clazz.getMethods()) {
+ if (method.getParameterCount() == 0 && method.getReturnType() !=
void.class) {
+ descriptorMap.put(method, new Descriptor(method));
+ }
+ }
+
+ currentDescriptorMap = new TreeMap<>(descriptorMap);
+ return Tuple2.of(descriptorMap, currentDescriptorMap);
+ }
do {
Field[] fields = clazz.getDeclaredFields();
boolean haveExpose = false, haveIgnore = false;
@@ -542,7 +575,7 @@ public class Descriptor {
Class<?> clz, boolean searchParent) {
List<Field> fieldList = new ArrayList<>();
Class<?> clazz = clz;
- Map<Tuple2<Class, String>, Method> methodMap = new HashMap<>();
+ Map<Tuple2<Class<?>, String>, Method> methodMap = new HashMap<>();
do {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
@@ -571,7 +604,7 @@ public class Descriptor {
}
// use TreeMap to sort to fix field order
- TreeMap<Field, Descriptor> descriptorMap = new TreeMap<>(fieldComparator);
+ TreeMap<Field, Descriptor> descriptorMap = new TreeMap<>(memberComparator);
for (Field field : fieldList) {
Class<?> fieldDeclaringClass = field.getDeclaringClass();
String fieldName = field.getName();
@@ -601,7 +634,7 @@ public class Descriptor {
setter = null;
}
}
- TypeRef fieldType = TypeRef.of(field.getGenericType());
+ TypeRef<?> fieldType = TypeRef.of(field.getGenericType());
descriptorMap.put(field, new Descriptor(field, fieldType, getter,
setter));
}
// Don't cache descriptors using a static `WeakHashMap<Class<?>,
SortedMap<Field, Descriptor>>`,
diff --git
a/java/fury-core/src/main/java/org/apache/fury/type/TypeResolutionContext.java
b/java/fury-core/src/main/java/org/apache/fury/type/TypeResolutionContext.java
new file mode 100644
index 00000000..aa5d1506
--- /dev/null
+++
b/java/fury-core/src/main/java/org/apache/fury/type/TypeResolutionContext.java
@@ -0,0 +1,105 @@
+/*
+ * 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.type;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import org.apache.fury.annotation.Internal;
+import org.apache.fury.reflect.TypeRef;
+
+@Internal
+public class TypeResolutionContext {
+ private final CustomTypeRegistry customTypeRegistry;
+ private final LinkedHashSet<TypeRef<?>> walkedTypePath;
+ private final Set<Class<?>> synthesizedBeanTypes;
+
+ public TypeResolutionContext(CustomTypeRegistry customTypeRegistry) {
+ this.customTypeRegistry = customTypeRegistry;
+ walkedTypePath = new LinkedHashSet<>();
+ synthesizedBeanTypes = Collections.emptySet();
+ }
+
+ public TypeResolutionContext(
+ CustomTypeRegistry customTypeRegistry,
+ LinkedHashSet<TypeRef<?>> walkedTypePath,
+ Set<Class<?>> synthesizedBeanTypes) {
+ this.customTypeRegistry = customTypeRegistry;
+ this.walkedTypePath = walkedTypePath;
+ this.synthesizedBeanTypes = synthesizedBeanTypes;
+ }
+
+ public CustomTypeRegistry getCustomTypeRegistry() {
+ return customTypeRegistry;
+ }
+
+ public LinkedHashSet<TypeRef<?>> getWalkedTypePath() {
+ return walkedTypePath;
+ }
+
+ public Set<Class<?>> getSynthesizedBeanTypes() {
+ return synthesizedBeanTypes;
+ }
+
+ public TypeRef<?> getEnclosingType() {
+ TypeRef<?> result = TypeRef.of(Object.class);
+ for (TypeRef<?> type : walkedTypePath) { // .getLast()
+ result = type;
+ }
+ return result;
+ }
+
+ public TypeResolutionContext appendTypePath(TypeRef<?>... typeRef) {
+ LinkedHashSet<TypeRef<?>> newWalkedTypePath = new
LinkedHashSet<>(walkedTypePath);
+ newWalkedTypePath.addAll(Arrays.asList(typeRef));
+ return new TypeResolutionContext(customTypeRegistry, newWalkedTypePath,
synthesizedBeanTypes);
+ }
+
+ public TypeResolutionContext appendTypePath(Class<?> clz) {
+ return appendTypePath(TypeRef.of(clz));
+ }
+
+ public TypeResolutionContext withSynthesizedBeanType(Class<?> clz) {
+ Set<Class<?>> newSynthesizedBeanTypes = new
HashSet<>(synthesizedBeanTypes);
+ newSynthesizedBeanTypes.add(clz);
+ return new TypeResolutionContext(customTypeRegistry, walkedTypePath,
newSynthesizedBeanTypes);
+ }
+
+ public boolean isSynthesizedBeanType(Class<?> cls) {
+ return synthesizedBeanTypes.contains(cls);
+ }
+
+ public void checkNoCycle(Class<?> clz) {
+ checkNoCycle(TypeRef.of(clz));
+ }
+
+ public void checkNoCycle(TypeRef<?> typeRef) {
+ if (walkedTypePath.contains(typeRef)
+ || walkedTypePath.contains(TypeRef.of(typeRef.getRawType()))) {
+ throw new UnsupportedOperationException(
+ "cyclic type is not supported. walkedTypePath: "
+ + walkedTypePath
+ + " seen type: "
+ + typeRef);
+ }
+ }
+}
diff --git a/java/fury-core/src/main/java/org/apache/fury/type/TypeUtils.java
b/java/fury-core/src/main/java/org/apache/fury/type/TypeUtils.java
index 2226128f..3262f7fd 100644
--- a/java/fury-core/src/main/java/org/apache/fury/type/TypeUtils.java
+++ b/java/fury-core/src/main/java/org/apache/fury/type/TypeUtils.java
@@ -336,7 +336,7 @@ public class TypeUtils {
if (type.getClass() == Class.class) {
return (Class<?>) type;
} else {
- return getRawType(typeRef.getType());
+ return getRawType(type);
}
}
@@ -549,6 +549,10 @@ public class TypeUtils {
.where(new TypeParameter<V>() {}, valueType);
}
+ public static boolean isContainer(Class<?> cls) {
+ return isCollection(cls) || isMap(cls) || cls.isArray();
+ }
+
public static boolean isCollection(Class<?> cls) {
return cls == ArrayList.class || Collection.class.isAssignableFrom(cls);
}
@@ -569,7 +573,11 @@ public class TypeUtils {
}
public static boolean isBean(Type type, CustomTypeRegistry customTypes) {
- return isBean(TypeRef.of(type), new LinkedHashSet<>(), customTypes);
+ return isBean(TypeRef.of(type), customTypes);
+ }
+
+ public static boolean isBean(TypeRef<?> type, CustomTypeRegistry
customTypes) {
+ return isBean(type, new TypeResolutionContext(customTypes));
}
/**
@@ -578,18 +586,18 @@ public class TypeUtils {
* nested class is ok.
*/
public static boolean isBean(TypeRef<?> typeRef) {
- return isBean(typeRef, new LinkedHashSet<>());
+ return isBean(typeRef, new
TypeResolutionContext(CustomTypeRegistry.EMPTY));
}
- private static boolean isBean(TypeRef<?> typeRef, LinkedHashSet<TypeRef<?>>
walkedTypePath) {
- return isBean(typeRef, walkedTypePath, CustomTypeRegistry.EMPTY);
+ public static boolean isBean(Class<?> clz, TypeResolutionContext ctx) {
+ return isBean(TypeRef.of(clz), ctx);
}
- private static boolean isBean(
- TypeRef<?> typeRef,
- LinkedHashSet<TypeRef<?>> walkedTypePath,
- CustomTypeRegistry customTypes) {
+ public static boolean isBean(TypeRef<?> typeRef, TypeResolutionContext ctx) {
Class<?> cls = getRawType(typeRef);
+ if (ctx.isSynthesizedBeanType(cls)) {
+ return true;
+ }
if (Modifier.isAbstract(cls.getModifiers()) ||
Modifier.isInterface(cls.getModifiers())) {
return false;
}
@@ -600,8 +608,7 @@ public class TypeUtils {
if (cls.getEnclosingClass() != null &&
!Modifier.isStatic(cls.getModifiers())) {
return false;
}
- LinkedHashSet<TypeRef<?>> newTypePath = new
LinkedHashSet<>(walkedTypePath);
- newTypePath.add(typeRef);
+ TypeResolutionContext newTypePath = ctx.appendTypePath(typeRef);
if (cls == Object.class) {
// return false for typeToken that point to un-specialized generic
type.
return false;
@@ -613,7 +620,6 @@ public class TypeUtils {
&& !ITERABLE_TYPE.isSupertypeOf(typeRef)
&& !MAP_TYPE.isSupertypeOf(typeRef);
if (maybe) {
- Class<?> enclosingType = enclosingType(newTypePath);
return Descriptor.getDescriptors(cls).stream()
.allMatch(
d -> {
@@ -621,9 +627,9 @@ public class TypeUtils {
// do field modifiers and getter/setter validation here, not
in getDescriptors.
// If Modifier.isFinal(d.getModifiers()), use reflection
// private field that doesn't have getter/setter will be
handled by reflection.
- return customTypes.hasCodec(enclosingType, t.getRawType())
- || isSupported(t, newTypePath, customTypes)
- || isBean(t, newTypePath, customTypes);
+ return ctx.getCustomTypeRegistry().hasCodec(cls,
t.getRawType())
+ || isSupported(t, newTypePath)
+ || isBean(t, newTypePath);
});
} else {
return false;
@@ -635,13 +641,10 @@ public class TypeUtils {
/** Check if <code>typeToken</code> is supported by row-format. */
public static boolean isSupported(TypeRef<?> typeRef) {
- return isSupported(typeRef, new LinkedHashSet<>(),
CustomTypeRegistry.EMPTY);
+ return isSupported(typeRef, new
TypeResolutionContext(CustomTypeRegistry.EMPTY));
}
- private static boolean isSupported(
- TypeRef<?> typeRef,
- LinkedHashSet<TypeRef<?>> walkedTypePath,
- CustomTypeRegistry customTypes) {
+ private static boolean isSupported(TypeRef<?> typeRef, TypeResolutionContext
ctx) {
Class<?> cls = getRawType(typeRef);
if (!Modifier.isPublic(cls.getModifiers())) {
return false;
@@ -657,7 +660,8 @@ public class TypeUtils {
return isSupported(Objects.requireNonNull(typeRef.getComponentType()));
} else if (ITERABLE_TYPE.isSupertypeOf(typeRef)) {
TypeRef<?> elementType = getElementType(typeRef);
- if (customTypes.canConstructCollection(typeRef.getRawType(),
elementType.getRawType())) {
+ if (ctx.getCustomTypeRegistry()
+ .canConstructCollection(typeRef.getRawType(),
elementType.getRawType())) {
return true;
}
boolean isSuperOfArrayList = cls.isAssignableFrom(ArrayList.class);
@@ -675,14 +679,8 @@ public class TypeUtils {
Tuple2<TypeRef<?>, TypeRef<?>> mapKeyValueType =
getMapKeyValueType(typeRef);
return isSupported(mapKeyValueType.f0) &&
isSupported(mapKeyValueType.f1);
} else {
- if (walkedTypePath.contains(typeRef)) {
- throw new UnsupportedOperationException(
- "cyclic type is not supported. walkedTypePath: " + walkedTypePath);
- } else {
- LinkedHashSet<TypeRef<?>> newTypePath = new
LinkedHashSet<>(walkedTypePath);
- newTypePath.add(typeRef);
- return isBean(typeRef, newTypePath);
- }
+ ctx.checkNoCycle(typeRef);
+ return isBean(typeRef, ctx.appendTypePath(typeRef));
}
}
@@ -699,59 +697,59 @@ public class TypeUtils {
public static LinkedHashSet<Class<?>> listBeansRecursiveInclusive(
Class<?> beanClass, CustomTypeRegistry customTypes) {
- return listBeansRecursiveInclusive(beanClass, new LinkedHashSet<>(),
customTypes);
+ TypeResolutionContext ctx = new TypeResolutionContext(customTypes);
+ if (beanClass.isInterface()) {
+ ctx = ctx.withSynthesizedBeanType(beanClass);
+ }
+ return listBeansRecursiveInclusive(beanClass, ctx);
}
private static LinkedHashSet<Class<?>> listBeansRecursiveInclusive(
- Class<?> beanClass,
- LinkedHashSet<TypeRef<?>> walkedTypePath,
- CustomTypeRegistry customTypes) {
+ Class<?> beanClass, TypeResolutionContext ctx) {
LinkedHashSet<Class<?>> beans = new LinkedHashSet<>();
- Class<?> enclosingType = enclosingType(walkedTypePath);
- if (customTypes.hasCodec(enclosingType, beanClass)) {
+ Class<?> enclosingType = ctx.getEnclosingType().getRawType();
+ if (ctx.getCustomTypeRegistry().hasCodec(enclosingType, beanClass)) {
return beans;
}
- if (isBean(beanClass, customTypes)) {
+ if (isBean(beanClass, ctx)) {
beans.add(beanClass);
}
LinkedHashSet<TypeRef<?>> typeRefs = new LinkedHashSet<>();
List<Descriptor> descriptors = Descriptor.getDescriptors(beanClass);
+ TypeResolutionContext newCtx = ctx;
for (Descriptor descriptor : descriptors) {
TypeRef<?> typeRef = descriptor.getTypeRef();
+ Class<?> rawType = typeRef.getRawType();
typeRefs.add(descriptor.getTypeRef());
typeRefs.addAll(getAllTypeArguments(typeRef));
+ if (beanClass.isInterface() && typeRef.getRawType().isInterface() &&
!isContainer(rawType)) {
+ newCtx = newCtx.withSynthesizedBeanType(typeRef.getRawType());
+ }
}
for (TypeRef<?> typeToken : typeRefs) {
Class<?> type = getRawType(typeToken);
- if (isBean(type, customTypes)) {
- if (walkedTypePath.contains(typeToken)) {
- throw new UnsupportedOperationException(
- "cyclic type is not supported. walkedTypePath: " +
walkedTypePath);
- } else {
- LinkedHashSet<TypeRef<?>> newPath = new
LinkedHashSet<>(walkedTypePath);
- newPath.add(typeToken);
- beans.addAll(listBeansRecursiveInclusive(type, newPath,
customTypes));
- }
- } else if (isCollection(type)) {
+ if (isCollection(type)) {
TypeRef<?> elementType = getElementType(typeToken);
- LinkedHashSet<TypeRef<?>> newPath = new
LinkedHashSet<>(walkedTypePath);
- newPath.add(elementType);
- beans.addAll(listBeansRecursiveInclusive(elementType.getClass(),
newPath, customTypes));
+ while (isContainer(elementType.getRawType())) {
+ elementType = getElementType(elementType);
+ }
+ beans.addAll(
+ listBeansRecursiveInclusive(
+ elementType.getRawType(), newCtx.appendTypePath(elementType)));
} else if (isMap(type)) {
Tuple2<TypeRef<?>, TypeRef<?>> mapKeyValueType =
getMapKeyValueType(typeToken);
- LinkedHashSet<TypeRef<?>> newPath = new
LinkedHashSet<>(walkedTypePath);
- newPath.add(mapKeyValueType.f0);
- newPath.add(mapKeyValueType.f1);
- beans.addAll(
- listBeansRecursiveInclusive(mapKeyValueType.f0.getRawType(),
newPath, customTypes));
- beans.addAll(
- listBeansRecursiveInclusive(mapKeyValueType.f1.getRawType(),
newPath, customTypes));
+ TypeResolutionContext mapCtx =
+ newCtx.appendTypePath(mapKeyValueType.f0, mapKeyValueType.f1);
+
beans.addAll(listBeansRecursiveInclusive(mapKeyValueType.f0.getRawType(),
mapCtx));
+
beans.addAll(listBeansRecursiveInclusive(mapKeyValueType.f1.getRawType(),
mapCtx));
} else if (type.isArray()) {
Class<?> arrayComponent = getArrayComponent(type);
- LinkedHashSet<TypeRef<?>> newPath = new
LinkedHashSet<>(walkedTypePath);
- newPath.add(TypeRef.of(arrayComponent));
- beans.addAll(listBeansRecursiveInclusive(arrayComponent, newPath,
customTypes));
+ beans.addAll(
+ listBeansRecursiveInclusive(arrayComponent,
newCtx.appendTypePath(arrayComponent)));
+ } else if (isBean(type, newCtx)) {
+ ctx.checkNoCycle(typeToken);
+ beans.addAll(listBeansRecursiveInclusive(type,
newCtx.appendTypePath(typeToken)));
}
}
return beans;
@@ -809,12 +807,4 @@ public class TypeUtils {
return pkg + "." + className;
}
}
-
- private static Class<?> enclosingType(LinkedHashSet<TypeRef<?>> newTypePath)
{
- Class<?> result = Object.class;
- for (TypeRef<?> type : newTypePath) {
- result = type.getRawType();
- }
- return result;
- }
}
diff --git
a/java/fury-core/src/test/java/org/apache/fury/CrossLanguageTest.java
b/java/fury-core/src/test/java/org/apache/fury/CrossLanguageTest.java
index 1ed9474d..bc256672 100644
--- a/java/fury-core/src/test/java/org/apache/fury/CrossLanguageTest.java
+++ b/java/fury-core/src/test/java/org/apache/fury/CrossLanguageTest.java
@@ -467,7 +467,7 @@ public class CrossLanguageTest extends FuryTestBase {
ObjectSerializer.class.getDeclaredMethod("computeStructHash",
Fury.class, Collection.class);
method.setAccessible(true);
Collection<Descriptor> descriptors =
- fury.getClassResolver().getAllDescriptorsMap(ComplexObject1.class,
true).values();
+ fury.getClassResolver().getFieldDescriptors(ComplexObject1.class,
true);
descriptors =
fury.getClassResolver().createDescriptorGrouper(descriptors,
false).getSortedDescriptors();
Integer hash = (Integer) method.invoke(serializer, fury, descriptors);
diff --git a/java/fury-format/README.md b/java/fury-format/README.md
index 407479ed..4dee94f7 100644
--- a/java/fury-format/README.md
+++ b/java/fury-format/README.md
@@ -13,16 +13,16 @@ Fury row format is heavily inspired by spark tungsten row
format, but with chang
The initial fury java row data structure implementation is modified from spark
unsafe row/writer.
It is possible to register custom type handling and collection factories for
the row format -
-see Encoders.registerCustomCodec and Encoders.registerCustomCollectionFactory.
+see Encoders.registerCustomCodec and Encoders.registerCustomCollectionFactory.
For an interface,
+Fury can synthesize a simple value implementation, such as the UuidType below.
A short example:
```
-@Data
-public static class UuidType {
- public UUID f1;
- public UUID[] f2;
- public SortedSet<UUID> f3;
+public interface UuidType {
+ UUID f1();
+ UUID[] f2();
+ SortedSet<UUID> f3();
}
static class UuidEncoder implements CustomCodec.MemoryBufferCodec<UUID> {
diff --git
a/java/fury-format/src/main/java/org/apache/fury/format/encoder/ArrayDataForEach.java
b/java/fury-format/src/main/java/org/apache/fury/format/encoder/ArrayDataForEach.java
index 65fbdff3..aaf711c5 100644
---
a/java/fury-format/src/main/java/org/apache/fury/format/encoder/ArrayDataForEach.java
+++
b/java/fury-format/src/main/java/org/apache/fury/format/encoder/ArrayDataForEach.java
@@ -31,7 +31,9 @@ import org.apache.fury.codegen.Expression.AbstractExpression;
import org.apache.fury.format.row.binary.BinaryArray;
import org.apache.fury.format.row.binary.BinaryUtils;
import org.apache.fury.format.type.CustomTypeEncoderRegistry;
+import org.apache.fury.format.type.CustomTypeHandler;
import org.apache.fury.reflect.TypeRef;
+import org.apache.fury.type.TypeResolutionContext;
import org.apache.fury.type.TypeUtils;
import org.apache.fury.util.Preconditions;
import org.apache.fury.util.StringUtils;
@@ -85,8 +87,13 @@ public class ArrayDataForEach extends AbstractExpression {
} else {
accessType = TypeRef.of(customEncoder.encodedType());
}
- this.accessMethod = BinaryUtils.getElemAccessMethodName(accessType);
- this.elemType = BinaryUtils.getElemReturnType(accessType);
+ CustomTypeHandler customTypeHandler =
CustomTypeEncoderRegistry.customTypeHandler();
+ TypeResolutionContext ctx = new TypeResolutionContext(customTypeHandler);
+ if (inputArrayData.type().getRawType().isInterface() &&
elemType.getRawType().isInterface()) {
+ ctx = ctx.withSynthesizedBeanType(elemType.getRawType());
+ }
+ this.accessMethod = BinaryUtils.getElemAccessMethodName(accessType, ctx);
+ this.elemType = BinaryUtils.getElemReturnType(accessType, ctx);
this.notNullAction = notNullAction;
this.nullAction = nullAction;
}
diff --git
a/java/fury-format/src/main/java/org/apache/fury/format/encoder/ArrayEncoderBuilder.java
b/java/fury-format/src/main/java/org/apache/fury/format/encoder/ArrayEncoderBuilder.java
index 4e5479d7..1a5ee5bd 100644
---
a/java/fury-format/src/main/java/org/apache/fury/format/encoder/ArrayEncoderBuilder.java
+++
b/java/fury-format/src/main/java/org/apache/fury/format/encoder/ArrayEncoderBuilder.java
@@ -48,7 +48,6 @@ public class ArrayEncoderBuilder extends
BaseBinaryEncoderBuilder {
private static final TypeRef<Field> ARROW_FIELD_TYPE =
TypeRef.of(Field.class);
private final TypeRef<?> arrayToken;
- private final Expression emptyArrayInstance;
public ArrayEncoderBuilder(Class<?> arrayCls, Class<?> beanClass) {
this(TypeRef.of(arrayCls), TypeRef.of(beanClass));
@@ -79,7 +78,6 @@ public class ArrayEncoderBuilder extends
BaseBinaryEncoderBuilder {
Expression.Literal.ofClass(beanType.getRawType()),
Expression.Literal.ofInt(0)),
TypeRef.of(Array.newInstance(beanType.getRawType(),
0).getClass())));
- emptyArrayInstance = new Expression.Reference(ROOT_EMPTY_ARRAY_NAME);
}
@Override
@@ -148,7 +146,7 @@ public class ArrayEncoderBuilder extends
BaseBinaryEncoderBuilder {
Expression.Reference fieldExpr = new Expression.Reference(FIELD_NAME,
ARROW_FIELD_TYPE, false);
Expression listExpression =
- serializeForArrayByWriter(emptyArrayInstance, array, arrayWriter,
arrayToken, fieldExpr);
+ serializeForArrayByWriter(array, arrayWriter, arrayToken, fieldExpr);
expressions.add(listExpression);
@@ -183,8 +181,7 @@ public class ArrayEncoderBuilder extends
BaseBinaryEncoderBuilder {
arrayData,
elemType,
(i, value) ->
- new Expression.Invoke(
- collection, "add", deserializeFor(emptyArrayInstance,
value, elemType)),
+ new Expression.Invoke(collection, "add", deserializeFor(value,
elemType, typeCtx)),
i -> new Expression.Invoke(collection, "add",
ExpressionUtils.nullValue(elemType)));
return new Expression.ListExpression(collection, addElemsOp, collection);
}
diff --git
a/java/fury-format/src/main/java/org/apache/fury/format/encoder/BaseBinaryEncoderBuilder.java
b/java/fury-format/src/main/java/org/apache/fury/format/encoder/BaseBinaryEncoderBuilder.java
index 7125de0a..524ec8eb 100644
---
a/java/fury-format/src/main/java/org/apache/fury/format/encoder/BaseBinaryEncoderBuilder.java
+++
b/java/fury-format/src/main/java/org/apache/fury/format/encoder/BaseBinaryEncoderBuilder.java
@@ -67,6 +67,7 @@ import org.apache.fury.format.type.DataTypes;
import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.reflect.ReflectionUtils;
import org.apache.fury.reflect.TypeRef;
+import org.apache.fury.type.TypeResolutionContext;
import org.apache.fury.type.TypeUtils;
import org.apache.fury.util.DateTimeUtils;
import org.apache.fury.util.Preconditions;
@@ -95,6 +96,7 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
protected final Map<TypeRef<?>, Reference> rowWriterMap = new HashMap<>();
protected final CustomTypeHandler customTypeHandler =
CustomTypeEncoderRegistry.customTypeHandler();
+ protected final TypeResolutionContext typeCtx;
public BaseBinaryEncoderBuilder(CodegenContext context, Class<?> beanClass) {
this(context, TypeRef.of(beanClass));
@@ -107,6 +109,12 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
ctx.addImport(BinaryRow.class.getPackage().getName() + ".*");
ctx.addImport(BinaryWriter.class.getPackage().getName() + ".*");
ctx.addImport(Schema.class.getPackage().getName() + ".*");
+ TypeResolutionContext typeCtx = new
TypeResolutionContext(customTypeHandler);
+ typeCtx.appendTypePath(beanClass);
+ if (beanClass.isInterface()) {
+ typeCtx = typeCtx.withSynthesizedBeanType(beanClass);
+ }
+ this.typeCtx = typeCtx;
}
public String codecClassName(Class<?> beanClass) {
@@ -140,7 +148,6 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
* writer</code>
*/
protected Expression serializeFor(
- Expression bean,
Expression ordinal,
Expression inputObject,
Expression writer,
@@ -157,10 +164,10 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
"rewrittenValue",
rewrittenType,
true,
- bean,
+ new Expression.Null(beanType, true),
inputObject);
Expression doSerialize =
- serializeFor(bean, ordinal, newInputObject, writer, rewrittenType,
arrowField);
+ serializeFor(ordinal, newInputObject, writer, rewrittenType,
arrowField);
return new If(
ExpressionUtils.eqNull(inputObject),
new Invoke(writer, "setNullAt", ordinal),
@@ -224,7 +231,7 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
// but don't setOffsetAndSize for array.
Invoke offset =
new Invoke(writer, "writerIndex", "writerIndex",
TypeUtils.PRIMITIVE_INT_TYPE);
- Expression serializeArray = serializeForArray(bean, inputObject, writer,
typeRef, arrowField);
+ Expression serializeArray = serializeForArray(inputObject, writer,
typeRef, arrowField);
Arithmetic size =
ExpressionUtils.subtract(
new Invoke(writer, "writerIndex", "writerIndex",
TypeUtils.PRIMITIVE_INT_TYPE),
@@ -237,8 +244,8 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
new Invoke(writer, "setNullAt", ordinal),
expression);
} else if (TypeUtils.MAP_TYPE.isSupertypeOf(typeRef)) {
- return serializeForMap(bean, ordinal, writer, inputObject, typeRef,
arrowField);
- } else if (TypeUtils.isBean(rawType)) {
+ return serializeForMap(ordinal, writer, inputObject, typeRef,
arrowField);
+ } else if (TypeUtils.isBean(rawType, createElementTypeContext(typeRef))) {
return serializeForBean(ordinal, writer, inputObject, typeRef,
arrowField);
} else if (rawType == BinaryArray.class) {
Invoke writeExp =
@@ -270,21 +277,13 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
}
protected Expression serializeForArray(
- Expression bean,
- Expression inputObject,
- Expression writer,
- TypeRef<?> typeRef,
- Expression arrowField) {
+ Expression inputObject, Expression writer, TypeRef<?> typeRef,
Expression arrowField) {
Reference arrayWriter = getOrCreateArrayWriter(typeRef, arrowField,
writer);
- return serializeForArrayByWriter(bean, inputObject, arrayWriter, typeRef,
arrowField);
+ return serializeForArrayByWriter(inputObject, arrayWriter, typeRef,
arrowField);
}
protected Expression serializeForArrayByWriter(
- Expression bean,
- Expression inputObject,
- Expression arrayWriter,
- TypeRef<?> typeRef,
- Expression arrowField) {
+ Expression inputObject, Expression arrayWriter, TypeRef<?> typeRef,
Expression arrowField) {
StaticInvoke arrayElementField =
new StaticInvoke(
DataTypes.class, "arrayElementField", "elemField",
ARROW_FIELD_TYPE, false, arrowField);
@@ -301,7 +300,6 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
inputObject,
(i, value) ->
serializeFor(
- bean,
i,
value,
arrayWriter,
@@ -318,12 +316,7 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
listFromIterable,
(i, value) ->
serializeFor(
- bean,
- i,
- value,
- arrayWriter,
- TypeUtils.getElementType(typeRef),
- arrayElementField));
+ i, value, arrayWriter,
TypeUtils.getElementType(typeRef), arrayElementField));
return new ListExpression(reset, forEach, arrayWriter);
} else { // collection
Invoke size = new Invoke(inputObject, "size",
TypeUtils.PRIMITIVE_INT_TYPE);
@@ -333,12 +326,7 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
inputObject,
(i, value) ->
serializeFor(
- bean,
- i,
- value,
- arrayWriter,
- TypeUtils.getElementType(typeRef),
- arrayElementField));
+ i, value, arrayWriter,
TypeUtils.getElementType(typeRef), arrayElementField));
return new ListExpression(reset, forEach, arrayWriter);
}
}
@@ -362,7 +350,6 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
* row/array using given <code>writer</code>.
*/
protected Expression serializeForMap(
- Expression bean,
Expression ordinal,
Expression writer,
Expression inputObject,
@@ -398,8 +385,7 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
expressions.add(offset, preserve);
Invoke keySet = new Invoke(inputObject, "keySet", keySetType);
- Expression keySerializationExpr =
- serializeForArray(bean, keySet, writer, keySetType, keyArrayField);
+ Expression keySerializationExpr = serializeForArray(keySet, writer,
keySetType, keyArrayField);
expressions.add(keySet, keySerializationExpr);
expressions.add(
@@ -411,7 +397,7 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
Invoke values = new Invoke(inputObject, "values", valuesType);
Expression valueSerializationExpr =
- serializeForArray(bean, values, writer, valuesType, valueArrayField);
+ serializeForArray(values, writer, valuesType, valueArrayField);
expressions.add(values, valueSerializationExpr);
Arithmetic size =
@@ -524,7 +510,8 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
* Returns an expression that deserialize <code>value</code> as a java
object of type <code>
* typeToken</code>.
*/
- protected Expression deserializeFor(Expression bean, Expression value,
TypeRef<?> typeRef) {
+ protected Expression deserializeFor(
+ Expression value, TypeRef<?> typeRef, TypeResolutionContext ctx) {
Class<?> rawType = getRawType(typeRef);
CustomCodec<?, ?> customHandler =
customTypeHandler.findCodec(beanType.getRawType(), rawType);
if (customHandler != null) {
@@ -543,7 +530,7 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
"decodedValue",
typeRef,
true,
- bean,
+ new Expression.Null(beanType, true),
new Expression.Null(typeRef, true),
inputValue);
if (rawRewrittenType == MemoryBuffer.class) {
@@ -553,7 +540,7 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
} else if (rawRewrittenType == byte[].class) {
return newValue;
}
- return deserializeFor(bean, newValue, rewrittenType);
+ return deserializeFor(newValue, rewrittenType, ctx);
} else if (TypeUtils.isPrimitive(rawType) || TypeUtils.isBoxed(rawType)) {
return value;
} else if (rawType == BigDecimal.class) {
@@ -576,12 +563,12 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
} else if (rawType.isEnum()) {
return ExpressionUtils.valueOf(typeRef, value);
} else if (rawType.isArray()) {
- return deserializeForArray(bean, value, typeRef);
+ return deserializeForArray(value, typeRef);
} else if (TypeUtils.ITERABLE_TYPE.isSupertypeOf(typeRef)) {
- return deserializeForCollection(bean, value, typeRef);
+ return deserializeForCollection(value, typeRef);
} else if (TypeUtils.MAP_TYPE.isSupertypeOf(typeRef)) {
- return deserializeForMap(bean, value, typeRef);
- } else if (TypeUtils.isBean(rawType)) {
+ return deserializeForMap(value, typeRef);
+ } else if (TypeUtils.isBean(rawType, ctx)) {
return deserializeForBean(value, typeRef);
} else {
return deserializeForObject(value, typeRef);
@@ -602,7 +589,7 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
}
/** Returns an expression that deserialize <code>mapData</code> as a java
map. */
- protected Expression deserializeForMap(Expression bean, Expression mapData,
TypeRef<?> typeRef) {
+ protected Expression deserializeForMap(Expression mapData, TypeRef<?>
typeRef) {
Expression javaMap = newMap(typeRef);
@SuppressWarnings("unchecked")
TypeRef<?> supertype = ((TypeRef<? extends Map<?, ?>>)
typeRef).getSupertype(Map.class);
@@ -614,14 +601,14 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
Expression keyJavaArray;
Expression valueJavaArray;
if (TypeUtils.ITERABLE_TYPE.isSupertypeOf(keysType)) {
- keyJavaArray = deserializeForCollection(bean, keyArray, keysType);
+ keyJavaArray = deserializeForCollection(keyArray, keysType);
} else {
- keyJavaArray = deserializeForArray(bean, keyArray, keysType);
+ keyJavaArray = deserializeForArray(keyArray, keysType);
}
if (TypeUtils.ITERABLE_TYPE.isSupertypeOf(valuesType)) {
- valueJavaArray = deserializeForCollection(bean, valueArray, valuesType);
+ valueJavaArray = deserializeForCollection(valueArray, valuesType);
} else {
- valueJavaArray = deserializeForArray(bean, valueArray, valuesType);
+ valueJavaArray = deserializeForArray(valueArray, valuesType);
}
ZipForEach put =
@@ -634,8 +621,7 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
}
/** Returns an expression that deserialize <code>arrayData</code> as a java
collection. */
- protected Expression deserializeForCollection(
- Expression bean, Expression arrayData, TypeRef<?> typeRef) {
+ protected Expression deserializeForCollection(Expression arrayData,
TypeRef<?> typeRef) {
Expression collection = newCollection(arrayData, typeRef);
try {
TypeRef<?> elemType = TypeUtils.getElementType(typeRef);
@@ -643,7 +629,11 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
new ArrayDataForEach(
arrayData,
elemType,
- (i, value) -> new Invoke(collection, "add", deserializeFor(bean,
value, elemType)),
+ (i, value) ->
+ new Invoke(
+ collection,
+ "add",
+ deserializeFor(value, elemType,
createElementTypeContext(elemType))),
i -> new Invoke(collection, "add",
ExpressionUtils.nullValue(elemType)));
return new ListExpression(collection, addElemsOp, collection);
} catch (Exception e) {
@@ -712,7 +702,6 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
* <code>rootJavaArray</code>.
*/
protected Expression deserializeForMultiDimensionArray(
- Expression bean,
Expression arrayData,
Expression rootJavaArray,
int numDimensions,
@@ -729,8 +718,7 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
Expression[] newIndexes = Arrays.copyOf(indexes, indexes.length +
1);
newIndexes[indexes.length] = i;
Expression elemArr =
- deserializeForArray(
- bean, value,
Objects.requireNonNull(typeRef.getComponentType()));
+ deserializeForArray(value,
Objects.requireNonNull(typeRef.getComponentType()));
return new AssignArrayElem(rootJavaArray, elemArr, newIndexes);
});
} else {
@@ -741,7 +729,7 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
Expression[] newIndexes = Arrays.copyOf(indexes, indexes.length +
1);
newIndexes[indexes.length] = i;
return deserializeForMultiDimensionArray(
- bean, value, rootJavaArray, numDimensions - 1, elemType,
newIndexes);
+ value, rootJavaArray, numDimensions - 1, elemType, newIndexes);
});
}
}
@@ -750,8 +738,7 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
* Return an expression that deserialize <code>arrayData</code>. If array is
multi-array, forward
* to {@link BaseBinaryEncoderBuilder#deserializeForMultiDimensionArray}
*/
- protected Expression deserializeForArray(
- Expression bean, Expression arrayData, TypeRef<?> typeRef) {
+ protected Expression deserializeForArray(Expression arrayData, TypeRef<?>
typeRef) {
int numDimensions = TypeUtils.getArrayDimensions(typeRef);
if (numDimensions > 1) {
// If some dimension's elements is all null, we take outer-most array as
null,
@@ -770,7 +757,7 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
Expression rootJavaMultiDimArray = new NewArray(innerElemClass,
numDimensions, dimensions);
Expression op =
deserializeForMultiDimensionArray(
- bean, arrayData, rootJavaMultiDimArray, numDimensions, typeRef,
new Expression[0]);
+ arrayData, rootJavaMultiDimArray, numDimensions, typeRef, new
Expression[0]);
// although the value maybe null, we don't use this info, so we set
nullability to false.
return new If(
ExpressionUtils.notNull(dimensions),
@@ -802,7 +789,8 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
arrayData,
elemType,
(i, value) -> {
- Expression elemValue = deserializeFor(bean, value, elemType);
+ Expression elemValue =
+ deserializeFor(value, elemType,
createElementTypeContext(elemType));
return new AssignArrayElem(javaArray, elemValue, i);
});
// add javaArray at last as expression value
@@ -818,4 +806,14 @@ public abstract class BaseBinaryEncoderBuilder extends
CodecBuilder {
protected Expression deserializeForObject(Expression value, TypeRef<?>
typeRef) {
return new Invoke(furyRef, "deserialize", typeRef, value);
}
+
+ protected TypeResolutionContext createElementTypeContext(TypeRef<?>
elemType) {
+ TypeResolutionContext newTypeCtx;
+ if (elemType.isInterface() && beanClass.isInterface()) {
+ newTypeCtx = typeCtx.withSynthesizedBeanType(elemType.getRawType());
+ } else {
+ newTypeCtx = typeCtx;
+ }
+ return newTypeCtx;
+ }
}
diff --git
a/java/fury-format/src/main/java/org/apache/fury/format/encoder/MapEncoderBuilder.java
b/java/fury-format/src/main/java/org/apache/fury/format/encoder/MapEncoderBuilder.java
index cf0d0fc6..a005340f 100644
---
a/java/fury-format/src/main/java/org/apache/fury/format/encoder/MapEncoderBuilder.java
+++
b/java/fury-format/src/main/java/org/apache/fury/format/encoder/MapEncoderBuilder.java
@@ -53,7 +53,6 @@ public class MapEncoderBuilder extends
BaseBinaryEncoderBuilder {
private static final String ROOT_VALUE_WRITER_NAME = "valueArrayWriter";
private static final TypeRef<Field> ARROW_FIELD_TYPE =
TypeRef.of(Field.class);
- private static final Expression MAP_TYPE_EXPR =
Expression.Literal.ofClass(Map.class);
private final TypeRef<?> mapToken;
public MapEncoderBuilder(Class<?> mapCls, Class<?> keyClass) {
@@ -181,7 +180,7 @@ public class MapEncoderBuilder extends
BaseBinaryEncoderBuilder {
expressions.add(
new Expression.Invoke(keyArrayWriter, "writeDirectly",
Expression.Literal.ofInt(-1)));
Expression keySerializationExpr =
- serializeForArrayByWriter(MAP_TYPE_EXPR, keySet, keyArrayWriter,
keySetType, keyFieldExpr);
+ serializeForArrayByWriter(keySet, keyArrayWriter, keySetType,
keyFieldExpr);
Expression.Invoke keyArray =
new Expression.Invoke(keyArrayWriter, "toArray",
TypeRef.of(BinaryArray.class));
expressions.add(map);
@@ -196,7 +195,7 @@ public class MapEncoderBuilder extends
BaseBinaryEncoderBuilder {
Expression.Invoke values = new Expression.Invoke(map, "values",
valuesType);
Expression valueSerializationExpr =
- serializeForArrayByWriter(MAP_TYPE_EXPR, values, valArrayWriter,
valuesType, valFieldExpr);
+ serializeForArrayByWriter(values, valArrayWriter, valuesType,
valFieldExpr);
Expression.Invoke valArray =
new Expression.Invoke(valArrayWriter, "toArray",
TypeRef.of(BinaryArray.class));
@@ -213,6 +212,7 @@ public class MapEncoderBuilder extends
BaseBinaryEncoderBuilder {
* Returns an expression that deserialize <code>row</code> as a java bean of
type {@link
* MapEncoderBuilder#mapToken}.
*/
+ @Override
public Expression buildDecodeExpression() {
Expression.ListExpression expressions = new Expression.ListExpression();
Expression map = newMap(mapToken);
@@ -238,14 +238,14 @@ public class MapEncoderBuilder extends
BaseBinaryEncoderBuilder {
Expression keyJavaArray;
Expression valueJavaArray;
if (TypeUtils.ITERABLE_TYPE.isSupertypeOf(keysType)) {
- keyJavaArray = deserializeForCollection(MAP_TYPE_EXPR, keyArrayRef,
keysType);
+ keyJavaArray = deserializeForCollection(keyArrayRef, keysType);
} else {
- keyJavaArray = deserializeForArray(MAP_TYPE_EXPR, keyArrayRef, keysType);
+ keyJavaArray = deserializeForArray(keyArrayRef, keysType);
}
if (TypeUtils.ITERABLE_TYPE.isSupertypeOf(valuesType)) {
- valueJavaArray = deserializeForCollection(MAP_TYPE_EXPR, valArrayRef,
valuesType);
+ valueJavaArray = deserializeForCollection(valArrayRef, valuesType);
} else {
- valueJavaArray = deserializeForArray(MAP_TYPE_EXPR, valArrayRef,
valuesType);
+ valueJavaArray = deserializeForArray(valArrayRef, valuesType);
}
Expression.ZipForEach put =
diff --git
a/java/fury-format/src/main/java/org/apache/fury/format/encoder/RowEncoderBuilder.java
b/java/fury-format/src/main/java/org/apache/fury/format/encoder/RowEncoderBuilder.java
index 6ea20553..4b56feac 100644
---
a/java/fury-format/src/main/java/org/apache/fury/format/encoder/RowEncoderBuilder.java
+++
b/java/fury-format/src/main/java/org/apache/fury/format/encoder/RowEncoderBuilder.java
@@ -48,6 +48,7 @@ import org.apache.fury.logging.Logger;
import org.apache.fury.logging.LoggerFactory;
import org.apache.fury.reflect.TypeRef;
import org.apache.fury.type.Descriptor;
+import org.apache.fury.type.TypeResolutionContext;
import org.apache.fury.type.TypeUtils;
import org.apache.fury.util.GraalvmSupport;
import org.apache.fury.util.Preconditions;
@@ -61,10 +62,13 @@ public class RowEncoderBuilder extends
BaseBinaryEncoderBuilder {
static final String ROOT_ROW_NAME = "row";
static final String ROOT_ROW_WRITER_NAME = "rowWriter";
+ private final String className;
private final SortedMap<String, Descriptor> descriptorsMap;
private final Schema schema;
protected static final String BEAN_CLASS_NAME = "beanClass";
protected Reference beanClassRef = new Reference(BEAN_CLASS_NAME,
CLASS_TYPE);
+ private final CodegenContext generatedBeanImpl;
+ private final String generatedBeanImplName;
public RowEncoderBuilder(Class<?> beanClass) {
this(TypeRef.of(beanClass));
@@ -72,7 +76,9 @@ public class RowEncoderBuilder extends
BaseBinaryEncoderBuilder {
public RowEncoderBuilder(TypeRef<?> beanType) {
super(new CodegenContext(), beanType);
- Preconditions.checkArgument(TypeUtils.isBean(beanType.getType(),
customTypeHandler));
+ Preconditions.checkArgument(
+ beanClass.isInterface() || TypeUtils.isBean(beanType.getType(),
customTypeHandler));
+ className = codecClassName(beanClass);
this.schema = TypeInference.inferSchema(getRawType(beanType));
this.descriptorsMap = Descriptor.getDescriptorsMap(beanClass);
ctx.reserveName(ROOT_ROW_WRITER_NAME);
@@ -86,12 +92,19 @@ public class RowEncoderBuilder extends
BaseBinaryEncoderBuilder {
// non-public class is not accessible in other class.
clsExpr =
new Expression.StaticInvoke(
- Class.class, "forName", CLASS_TYPE, false,
Literal.ofClass(beanClass));
+ Class.class, "forName", CLASS_TYPE, false,
Literal.ofString(ctx.type(beanClass)));
}
ctx.addField(Class.class, "beanClass", clsExpr);
ctx.addImports(Field.class, Schema.class);
ctx.addImports(Row.class, ArrayData.class, MapData.class);
ctx.addImports(BinaryRow.class, BinaryArray.class, BinaryMap.class);
+ if (beanClass.isInterface()) {
+ generatedBeanImplName = beanClass.getSimpleName() + "GeneratedImpl";
+ generatedBeanImpl = buildImplClass();
+ } else {
+ generatedBeanImplName = null;
+ generatedBeanImpl = null;
+ }
}
@Override
@@ -102,7 +115,6 @@ public class RowEncoderBuilder extends
BaseBinaryEncoderBuilder {
@Override
public String genCode() {
ctx.setPackage(CodeGenerator.getPackage(beanClass));
- String className = codecClassName(beanClass);
ctx.setClassName(className);
// don't addImport(beanClass), because user class may name collide.
// janino don't support generics, so GeneratedCodec has no generics
@@ -141,6 +153,14 @@ public class RowEncoderBuilder extends
BaseBinaryEncoderBuilder {
long startTime = System.nanoTime();
String code = ctx.genCode();
+ // It would be nice if Expression let us write inner classes
+ if (generatedBeanImpl != null) {
+ int insertPoint = code.lastIndexOf('}');
+ code =
+ code.substring(0, insertPoint)
+ + generatedBeanImpl.genCode()
+ + code.substring(insertPoint);
+ }
long durationMs = (System.nanoTime() - startTime) / 1000;
LOG.info("Generate codec for class {} take {} us", beanClass, durationMs);
return code;
@@ -169,7 +189,7 @@ public class RowEncoderBuilder extends
BaseBinaryEncoderBuilder {
Expression.StaticInvoke field =
new Expression.StaticInvoke(
DataTypes.class, "fieldOfSchema", ARROW_FIELD_TYPE, false,
schemaExpr, ordinal);
- Expression fieldExpr = serializeFor(bean, ordinal, fieldValue, writer,
fieldType, field);
+ Expression fieldExpr = serializeFor(ordinal, fieldValue, writer,
fieldType, field);
expressions.add(fieldExpr);
}
expressions.add(
@@ -185,6 +205,13 @@ public class RowEncoderBuilder extends
BaseBinaryEncoderBuilder {
@Override
public Expression buildDecodeExpression() {
Reference row = new Reference(ROOT_ROW_NAME, binaryRowTypeToken, false);
+
+ addDecoderMethods();
+
+ if (generatedBeanImpl != null) {
+ return new Expression.Return(
+ new Expression.Reference("new " + generatedBeanImplName + "(row)"));
+ }
Expression bean = newBean();
int numFields = schema.getFields().size();
@@ -197,16 +224,42 @@ public class RowEncoderBuilder extends
BaseBinaryEncoderBuilder {
TypeRef<?> fieldType = d.getTypeRef();
Expression.Invoke isNullAt =
new Expression.Invoke(row, "isNullAt",
TypeUtils.PRIMITIVE_BOOLEAN_TYPE, ordinal);
- CustomCodec<?, ?> customEncoder =
- customTypeHandler.findCodec(beanClass, fieldType.getRawType());
+ Expression value =
+ new Expression.Variable(
+ "decoded" + i, new Expression.Reference("decode" + i + "(row)",
fieldType));
+ Expression setActionExpr = setFieldValue(bean, d, value);
+ Expression action = new Expression.If(ExpressionUtils.not(isNullAt),
setActionExpr);
+ expressions.add(action);
+ }
+
+ expressions.add(new Expression.Return(bean));
+ return expressions;
+ }
+
+ private void addDecoderMethods() {
+ Reference row = new Reference(ROOT_ROW_NAME, binaryRowTypeToken, false);
+ int numFields = schema.getFields().size();
+ for (int i = 0; i < numFields; i++) {
+ Literal ordinal = Literal.ofInt(i);
+ Descriptor d =
getDescriptorByFieldName(schema.getFields().get(i).getName());
+ TypeRef<?> fieldType = d.getTypeRef();
+ Class<?> rawFieldType = fieldType.getRawType();
+ TypeResolutionContext fieldCtx;
+ if (beanClass.isInterface() && rawFieldType.isInterface()) {
+ fieldCtx = typeCtx.withSynthesizedBeanType(rawFieldType);
+ } else {
+ fieldCtx = typeCtx;
+ }
+ CustomCodec<?, ?> customEncoder = customTypeHandler.findCodec(beanClass,
rawFieldType);
TypeRef<?> columnAccessType;
if (customEncoder == null) {
columnAccessType = fieldType;
} else {
columnAccessType = TypeRef.of(customEncoder.encodedType());
}
- String columnAccessMethodName =
BinaryUtils.getElemAccessMethodName(columnAccessType);
- TypeRef<?> colType = BinaryUtils.getElemReturnType(columnAccessType);
+ String columnAccessMethodName =
+ BinaryUtils.getElemAccessMethodName(columnAccessType, fieldCtx);
+ TypeRef<?> colType = BinaryUtils.getElemReturnType(columnAccessType,
fieldCtx);
Expression.Invoke columnValue =
new Expression.Invoke(
row,
@@ -215,14 +268,64 @@ public class RowEncoderBuilder extends
BaseBinaryEncoderBuilder {
colType,
false,
ordinal);
- Expression value = deserializeFor(bean, columnValue, fieldType);
- Expression setActionExpr = setFieldValue(bean, d, value);
- Expression action = new Expression.If(ExpressionUtils.not(isNullAt),
setActionExpr);
- expressions.add(action);
+ final Expression value =
+ new Expression.Return(deserializeFor(columnValue, fieldType,
fieldCtx));
+ ctx.addMethod(
+ "decode" + i,
+ value.doGenCode(ctx).code(),
+ fieldType.getRawType(),
+ BinaryRow.class,
+ ROOT_ROW_NAME);
}
+ }
- expressions.add(new Expression.Return(bean));
- return expressions;
+ private CodegenContext buildImplClass() {
+ Reference row = new Reference(ROOT_ROW_NAME, binaryRowTypeToken, false);
+ CodegenContext implClass = new CodegenContext();
+ implClass.setClassModifiers("final");
+ implClass.setClassName(generatedBeanImplName);
+ implClass.implementsInterfaces(implClass.type(beanClass));
+ implClass.addField(true, implClass.type(BinaryRow.class), "row", null);
+ implClass.addConstructor("this.row = row;", BinaryRow.class, "row");
+
+ int numFields = schema.getFields().size();
+ for (int i = 0; i < numFields; i++) {
+ Literal ordinal = Literal.ofInt(i);
+ Descriptor d =
getDescriptorByFieldName(schema.getFields().get(i).getName());
+ TypeRef<?> fieldType = d.getTypeRef();
+
+ Expression.Reference decodeValue =
+ new Expression.Reference("decode" + i + "(row)", fieldType);
+ Expression getterImpl;
+ if (fieldType.isPrimitive()) {
+ getterImpl = new Expression.Return(decodeValue);
+ } else {
+ String fieldName = "f" + i + "_" + d.getName();
+ implClass.addField(fieldType.getRawType(), fieldName);
+
+ Expression fieldRef = new Expression.Reference(fieldName, fieldType,
true);
+ Expression storeValue =
+ new Expression.SetField(new Expression.Reference("this"),
fieldName, decodeValue);
+ Expression loadIfFieldIsNull =
+ new Expression.If(new Expression.IsNull(fieldRef), storeValue);
+ Expression assigner;
+
+ if (d.isNullable()) {
+ Expression isNotNullAt =
+ new Expression.Not(
+ new Expression.Invoke(
+ row, "isNullAt", TypeUtils.PRIMITIVE_BOOLEAN_TYPE,
ordinal));
+ assigner = new Expression.If(isNotNullAt, loadIfFieldIsNull);
+ } else {
+ assigner = loadIfFieldIsNull;
+ }
+ getterImpl = new Expression.ListExpression(assigner, new
Expression.Return(fieldRef));
+ }
+ implClass.addMethod(
+ d.getName(), getterImpl.genCode(implClass).code(),
fieldType.getRawType());
+ }
+
+ return implClass;
}
private Descriptor getDescriptorByFieldName(String fieldName) {
diff --git
a/java/fury-format/src/main/java/org/apache/fury/format/row/binary/BinaryUtils.java
b/java/fury-format/src/main/java/org/apache/fury/format/row/binary/BinaryUtils.java
index f16ada2a..d488b0b1 100644
---
a/java/fury-format/src/main/java/org/apache/fury/format/row/binary/BinaryUtils.java
+++
b/java/fury-format/src/main/java/org/apache/fury/format/row/binary/BinaryUtils.java
@@ -21,12 +21,13 @@ package org.apache.fury.format.row.binary;
import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.reflect.TypeRef;
+import org.apache.fury.type.TypeResolutionContext;
import org.apache.fury.type.TypeUtils;
/** Util class for building generated binary encoder. */
@SuppressWarnings("UnstableApiUsage")
public class BinaryUtils {
- public static String getElemAccessMethodName(TypeRef<?> type) {
+ public static String getElemAccessMethodName(TypeRef<?> type,
TypeResolutionContext ctx) {
if (TypeUtils.PRIMITIVE_BYTE_TYPE.equals(type) ||
TypeUtils.BYTE_TYPE.equals(type)) {
return "getByte";
} else if (TypeUtils.PRIMITIVE_BOOLEAN_TYPE.equals(type)
@@ -57,7 +58,7 @@ public class BinaryUtils {
return "getArray";
} else if (TypeUtils.MAP_TYPE.isSupertypeOf(type)) {
return "getMap";
- } else if (TypeUtils.isBean(type)) {
+ } else if (TypeUtils.isBean(type, ctx)) {
return "getStruct";
} else {
// take unknown type as OBJECT_TYPE, return as sliced MemoryBuffer
@@ -66,7 +67,7 @@ public class BinaryUtils {
}
}
- public static TypeRef<?> getElemReturnType(TypeRef<?> type) {
+ public static TypeRef<?> getElemReturnType(TypeRef<?> type,
TypeResolutionContext ctx) {
if (TypeUtils.PRIMITIVE_BYTE_TYPE.equals(type) ||
TypeUtils.BYTE_TYPE.equals(type)) {
return TypeUtils.PRIMITIVE_BYTE_TYPE;
} else if (TypeUtils.PRIMITIVE_BOOLEAN_TYPE.equals(type)
@@ -95,7 +96,7 @@ public class BinaryUtils {
return TypeRef.of(BinaryArray.class);
} else if (TypeUtils.MAP_TYPE.isSupertypeOf(type)) {
return TypeRef.of(BinaryMap.class);
- } else if (TypeUtils.isBean(type)) {
+ } else if (TypeUtils.isBean(type, ctx)) {
return TypeRef.of(BinaryRow.class);
} else {
// take unknown type as OBJECT_TYPE, return as sliced MemoryBuffer
diff --git
a/java/fury-format/src/main/java/org/apache/fury/format/type/TypeInference.java
b/java/fury-format/src/main/java/org/apache/fury/format/type/TypeInference.java
index 7d3f963a..935a0cd9 100644
---
a/java/fury-format/src/main/java/org/apache/fury/format/type/TypeInference.java
+++
b/java/fury-format/src/main/java/org/apache/fury/format/type/TypeInference.java
@@ -24,7 +24,6 @@ import static org.apache.fury.type.TypeUtils.getRawType;
import java.util.Arrays;
import java.util.Collection;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -42,6 +41,7 @@ import org.apache.fury.format.encoder.CustomCodec;
import org.apache.fury.format.encoder.CustomCollectionFactory;
import org.apache.fury.reflect.TypeRef;
import org.apache.fury.type.Descriptor;
+import org.apache.fury.type.TypeResolutionContext;
import org.apache.fury.type.TypeUtils;
import org.apache.fury.util.DecimalUtils;
import org.apache.fury.util.Preconditions;
@@ -120,13 +120,18 @@ public class TypeInference {
}
private static Field inferField(TypeRef<?> arrayTypeRef, TypeRef<?> typeRef)
{
- LinkedHashSet<Class<?>> seenTypeSet = new LinkedHashSet<>();
+ TypeResolutionContext ctx =
+ new
TypeResolutionContext(CustomTypeEncoderRegistry.customTypeHandler());
+ Class<?> clz = getRawType(typeRef);
+ if (clz.isInterface()) {
+ ctx = ctx.withSynthesizedBeanType(clz);
+ }
String name = "";
if (arrayTypeRef != null) {
- Field f = inferField(DataTypes.ARRAY_ITEM_NAME, typeRef, seenTypeSet);
+ Field f = inferField(DataTypes.ARRAY_ITEM_NAME, typeRef, ctx);
return DataTypes.arrayField(name, f);
} else {
- return inferField("", typeRef, seenTypeSet);
+ return inferField("", typeRef, ctx);
}
}
@@ -136,19 +141,11 @@ public class TypeInference {
*
* @return DataType of a typeToken
*/
- private static Field inferField(
- String name, TypeRef<?> typeRef, LinkedHashSet<Class<?>> seenTypeSet) {
- return inferField(name, typeRef, seenTypeSet,
CustomTypeEncoderRegistry.customTypeHandler());
- }
-
- private static Field inferField(
- String name,
- TypeRef<?> typeRef,
- LinkedHashSet<Class<?>> seenTypeSet,
- CustomTypeHandler customTypes) {
+ private static Field inferField(String name, TypeRef<?> typeRef,
TypeResolutionContext ctx) {
Class<?> rawType = getRawType(typeRef);
- Class<?> enclosingType = enclosingType(seenTypeSet);
- CustomCodec<?, ?> customEncoder = customTypes.findCodec(enclosingType,
rawType);
+ Class<?> enclosingType = ctx.getEnclosingType().getRawType();
+ CustomCodec<?, ?> customEncoder =
+ ((CustomTypeHandler)
ctx.getCustomTypeRegistry()).findCodec(enclosingType, rawType);
if (customEncoder != null) {
return customEncoder.getField(name);
} else if (rawType == boolean.class) {
@@ -207,53 +204,44 @@ public class TypeInference {
} else if (rawType.isArray()) { // array
Field f =
inferField(
- DataTypes.ARRAY_ITEM_NAME,
- Objects.requireNonNull(typeRef.getComponentType()),
- seenTypeSet,
- customTypes);
+ DataTypes.ARRAY_ITEM_NAME,
Objects.requireNonNull(typeRef.getComponentType()), ctx);
return DataTypes.arrayField(name, f);
} else if (TypeUtils.ITERABLE_TYPE.isSupertypeOf(typeRef)) { // iterable
// when type is both iterable and bean, we take it as iterable in
row-format
- Field f =
- inferField(
- DataTypes.ARRAY_ITEM_NAME,
- TypeUtils.getElementType(typeRef),
- seenTypeSet,
- customTypes);
+ Field f = inferField(DataTypes.ARRAY_ITEM_NAME,
TypeUtils.getElementType(typeRef), ctx);
return DataTypes.arrayField(name, f);
} else if (TypeUtils.MAP_TYPE.isSupertypeOf(typeRef)) {
Tuple2<TypeRef<?>, TypeRef<?>> kvType =
TypeUtils.getMapKeyValueType(typeRef);
- Field keyField = inferField(MapVector.KEY_NAME, kvType.f0, seenTypeSet,
customTypes);
+ Field keyField = inferField(MapVector.KEY_NAME, kvType.f0, ctx);
// Map's keys must be non-nullable
FieldType keyFieldType =
new FieldType(
false, keyField.getType(), keyField.getDictionary(),
keyField.getMetadata());
keyField = DataTypes.field(keyField.getName(), keyFieldType,
keyField.getChildren());
- Field valueField = inferField(MapVector.VALUE_NAME, kvType.f1,
seenTypeSet, customTypes);
+ Field valueField = inferField(MapVector.VALUE_NAME, kvType.f1, ctx);
return DataTypes.mapField(name, keyField, valueField);
- } else if (TypeUtils.isBean(rawType, customTypes)) { // bean field
- if (seenTypeSet.contains(rawType)) {
- String msg =
- String.format(
- "circular references in bean class is not allowed, but got " +
"%s in %s",
- rawType, seenTypeSet);
- throw new UnsupportedOperationException(msg);
- }
+ } else if (TypeUtils.isBean(rawType, ctx)) { // bean field
+ ctx.checkNoCycle(rawType);
List<Field> fields =
Descriptor.getDescriptors(rawType).stream()
.map(
descriptor -> {
- LinkedHashSet<Class<?>> newSeenTypeSet = new
LinkedHashSet<>(seenTypeSet);
- newSeenTypeSet.add(rawType);
String n =
StringUtils.lowerCamelToLowerUnderscore(descriptor.getName());
- return inferField(n, descriptor.getTypeRef(),
newSeenTypeSet, customTypes);
+ TypeResolutionContext newCtx = ctx.appendTypePath(rawType);
+ TypeRef<?> fieldType = descriptor.getTypeRef();
+ Class<?> rawFieldType = getRawType(fieldType);
+ if (rawType.isInterface() && rawFieldType.isInterface()) {
+ newCtx = newCtx.withSynthesizedBeanType(rawFieldType);
+ }
+ return inferField(n, fieldType, newCtx);
})
.collect(Collectors.toList());
return DataTypes.structField(name, true, fields);
} else {
throw new UnsupportedOperationException(
String.format(
- "Unsupported type %s for field %s, seen type set is %s",
typeRef, name, seenTypeSet));
+ "Unsupported type %s for field %s, seen type set is %s",
+ typeRef, name, ctx.getWalkedTypePath()));
}
}
@@ -282,12 +270,4 @@ public class TypeInference {
Class<?> iterableType, Class<E> elementType, CustomCollectionFactory<E,
C> factory) {
CustomTypeEncoderRegistry.registerCustomCollection(iterableType,
elementType, factory);
}
-
- private static Class<?> enclosingType(LinkedHashSet<Class<?>> newTypePath) {
- Class<?> result = Object.class;
- for (Class<?> type : newTypePath) {
- result = type;
- }
- return result;
- }
}
diff --git
a/java/fury-format/src/test/java/org/apache/fury/format/encoder/ImplementInterfaceTest.java
b/java/fury-format/src/test/java/org/apache/fury/format/encoder/ImplementInterfaceTest.java
new file mode 100644
index 00000000..c2b47c33
--- /dev/null
+++
b/java/fury-format/src/test/java/org/apache/fury/format/encoder/ImplementInterfaceTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.format.encoder;
+
+import lombok.Data;
+import org.apache.fury.annotation.FuryField;
+import org.apache.fury.format.row.binary.BinaryRow;
+import org.apache.fury.memory.MemoryBuffer;
+import org.apache.fury.memory.MemoryUtils;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class ImplementInterfaceTest {
+
+ public interface InterfaceType {
+ int getF1();
+
+ String getF2();
+
+ NestedType getNested();
+
+ PoisonPill getPoison();
+ }
+
+ public interface NestedType {
+ @FuryField(nullable = false)
+ String getF3();
+ }
+
+ public static class PoisonPill {}
+
+ @Data
+ static class ImplementInterface implements InterfaceType {
+ public int f1;
+ public String f2;
+ public NestedType nested;
+ public PoisonPill poison = new PoisonPill();
+
+ public ImplementInterface(final int f1, final String f2) {
+ this.f1 = f1;
+ this.f2 = f2;
+ }
+ }
+
+ @Data
+ static class ImplementNestedType implements NestedType {
+ public String f3;
+
+ public ImplementNestedType(final String f3) {
+ this.f3 = f3;
+ }
+ }
+
+ static {
+ Encoders.registerCustomCodec(PoisonPill.class, new PoisonPillCodec());
+ }
+
+ @Test
+ public void testInterfaceTypes() {
+ final InterfaceType bean1 = new ImplementInterface(42, "42");
+ final RowEncoder<InterfaceType> encoder =
Encoders.bean(InterfaceType.class);
+ final BinaryRow row = encoder.toRow(bean1);
+ final MemoryBuffer buffer = MemoryUtils.wrap(row.toBytes());
+ row.pointTo(buffer, 0, buffer.size());
+ final InterfaceType deserializedBean = encoder.fromRow(row);
+ assertEquals(bean1, deserializedBean);
+ }
+
+ @Test
+ public void testNullValue() {
+ final InterfaceType bean1 = new ImplementInterface(42, null);
+ final RowEncoder<InterfaceType> encoder =
Encoders.bean(InterfaceType.class);
+ final BinaryRow row = encoder.toRow(bean1);
+ final MemoryBuffer buffer = MemoryUtils.wrap(row.toBytes());
+ row.pointTo(buffer, 0, buffer.size());
+ final InterfaceType deserializedBean = encoder.fromRow(row);
+ assertEquals(deserializedBean, bean1);
+ }
+
+ @Test
+ public void testNestedValue() {
+ final ImplementInterface bean1 = new ImplementInterface(42, "42");
+ bean1.nested = new ImplementNestedType("f3");
+ final RowEncoder<InterfaceType> encoder =
Encoders.bean(InterfaceType.class);
+ final BinaryRow row = encoder.toRow(bean1);
+ final MemoryBuffer buffer = MemoryUtils.wrap(row.toBytes());
+ row.pointTo(buffer, 0, buffer.size());
+ final InterfaceType deserializedBean = encoder.fromRow(row);
+ assertEquals(bean1, deserializedBean);
+ Assert.assertEquals(bean1.nested.getF3(),
deserializedBean.getNested().getF3());
+ }
+
+ private void assertEquals(final InterfaceType bean1, final InterfaceType
deserializedBean) {
+ Assert.assertNotSame(deserializedBean.getClass(), bean1.getClass());
+ Assert.assertEquals(deserializedBean.getF1(), bean1.getF1());
+ Assert.assertEquals(deserializedBean.getF2(), bean1.getF2());
+ }
+
+ static class PoisonPillCodec implements
CustomCodec.ByteArrayCodec<PoisonPill> {
+ @Override
+ public byte[] encode(final PoisonPill value) {
+ return new byte[0];
+ }
+
+ @Override
+ public PoisonPill decode(final byte[] value) {
+ throw new AssertionError();
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]