Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static org.apache.fory.type.TypeUtils.OBJECT_TYPE;
import static org.apache.fory.type.TypeUtils.STRING_TYPE;

import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.util.Collection;
import java.util.Map;
Expand All @@ -44,7 +45,9 @@
import org.apache.fory.serializer.ObjectSerializer;
import org.apache.fory.serializer.Serializer;
import org.apache.fory.serializer.Serializers;
import org.apache.fory.serializer.converter.FieldConverter;
import org.apache.fory.type.Descriptor;
import org.apache.fory.type.DescriptorBuilder;
import org.apache.fory.type.DescriptorGrouper;
import org.apache.fory.util.DefaultValueUtils;
import org.apache.fory.util.ExceptionUtils;
Expand Down Expand Up @@ -221,11 +224,25 @@ protected Expression createRecord(SortedMap<Integer, Expression> recordComponent
@Override
protected Expression setFieldValue(Expression bean, Descriptor descriptor, Expression value) {
if (descriptor.getField() == null) {
FieldConverter<?> converter = descriptor.getFieldConverter();
if (converter != null) {
Field field = converter.getField();
StaticInvoke converted =
new StaticInvoke(
converter.getClass(), "convertFrom", TypeRef.of(field.getType()), value);
Descriptor newDesc =
new DescriptorBuilder(descriptor)
.field(field)
.type(field.getType())
.typeRef(TypeRef.of(field.getType()))
.build();
return super.setFieldValue(bean, newDesc, converted);
}
// Field doesn't exist in current class, skip set this field value.
// Note that the field value shouldn't be an inlined value, otherwise field value read may
// be ignored.
// Add an ignored call here to make expression type to void.
return new Expression.StaticInvoke(ExceptionUtils.class, "ignore", value);
return new StaticInvoke(ExceptionUtils.class, "ignore", value);
}
return super.setFieldValue(bean, descriptor, value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
import org.apache.fory.resolver.XtypeResolver;
import org.apache.fory.serializer.CompatibleSerializer;
import org.apache.fory.serializer.NonexistentClass;
import org.apache.fory.serializer.converter.FieldConverter;
import org.apache.fory.serializer.converter.FieldConverters;
import org.apache.fory.type.Descriptor;
import org.apache.fory.type.FinalObjectTypeStub;
import org.apache.fory.type.GenericType;
Expand Down Expand Up @@ -276,6 +278,11 @@ public List<Descriptor> getDescriptors(TypeResolver resolver, Class<?> cls) {
descriptor = descriptor.copyWithTypeName(newDesc.getTypeName());
descriptors.add(descriptor);
} else {
FieldConverter<?> converter =
FieldConverters.getConverter(rawType, descriptor.getField());
if (converter != null) {
newDesc.setFieldConverter(converter);
}
descriptors.add(newDesc);
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,15 @@
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import org.apache.fory.collection.ClassValueCache;
import org.apache.fory.collection.Tuple2;
import org.apache.fory.memory.Platform;
import org.apache.fory.type.TypeUtils;
import org.apache.fory.util.GraalvmSupport;
Expand Down Expand Up @@ -494,20 +498,39 @@ public Object get(Object obj) {
}

static class GeneratedAccessor extends FieldAccessor {
private static final ClassValueCache<ConcurrentMap<String, Tuple2<MethodHandle, MethodHandle>>>
cache = ClassValueCache.newClassKeyCache(8);

private final MethodHandle getter;
private final MethodHandle setter;

protected GeneratedAccessor(Field field) {
super(field, -1);
MethodHandles.Lookup lookup = _JDKAccess._trustedLookup(field.getDeclaringClass());
ConcurrentMap<String, Tuple2<MethodHandle, MethodHandle>> map;
try {
this.getter =
lookup.findGetter(field.getDeclaringClass(), field.getName(), field.getType());
this.setter =
lookup.findSetter(field.getDeclaringClass(), field.getName(), field.getType());
} catch (IllegalAccessException | NoSuchFieldException ex) {
throw new RuntimeException(ex);
map = cache.get(field.getDeclaringClass(), ConcurrentHashMap::new);
} catch (Exception e) {
throw new RuntimeException(e);
}
MethodHandles.Lookup lookup = _JDKAccess._trustedLookup(field.getDeclaringClass());
Tuple2<MethodHandle, MethodHandle> tuple2 =
map.computeIfAbsent(
field.getName(),
k -> {
try {
MethodHandle getter =
lookup.findGetter(
field.getDeclaringClass(), field.getName(), field.getType());
MethodHandle setter =
lookup.findSetter(
field.getDeclaringClass(), field.getName(), field.getType());
return Tuple2.of(getter, setter);
} catch (IllegalAccessException | NoSuchFieldException ex) {
throw new RuntimeException(ex);
}
});
getter = tuple2.f0;
setter = tuple2.f1;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
Expand Down Expand Up @@ -402,6 +403,12 @@ public static List<Field> getFieldsWithoutSuperClasses(Class<?> cls, Set<Class<?
return fields;
}

public static List<Field> getSortedFields(Class<?> cls, boolean searchParent) {
List<Field> fields = getFields(cls, searchParent);
fields.sort(Comparator.comparing(f -> f.getName() + f.getDeclaringClass()));
return fields;
}

/** Get fields values from provided object. */
public static List<Object> getFieldValues(Collection<Field> fields, Object o) {
List<Object> results = new ArrayList<>(fields.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.apache.fory.resolver.ClassResolver;
import org.apache.fory.resolver.RefResolver;
import org.apache.fory.resolver.TypeResolver;
import org.apache.fory.serializer.converter.FieldConverter;
import org.apache.fory.type.Descriptor;
import org.apache.fory.type.DescriptorGrouper;
import org.apache.fory.type.FinalObjectTypeStub;
Expand Down Expand Up @@ -990,6 +991,7 @@ public static class InternalFieldInfo {
protected final short classId;
protected final String qualifiedFieldName;
protected final FieldAccessor fieldAccessor;
protected final FieldConverter<?> fieldConverter;
protected boolean nullable;
protected boolean trackingRef;

Expand All @@ -998,6 +1000,7 @@ private InternalFieldInfo(Fory fory, Descriptor d, short classId) {
this.classId = classId;
this.qualifiedFieldName = d.getDeclaringClass() + "." + d.getName();
this.fieldAccessor = d.getField() != null ? FieldAccessor.createAccessor(d.getField()) : null;
fieldConverter = d.getFieldConverter();
ForyField foryField = d.getForyField();
nullable = d.isNullable();
if (fory.trackingRef()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,15 +200,19 @@ public T read(MemoryBuffer buffer) {
fieldAccessor.putObject(obj, fieldValue);
}
} else {
// Skip the field value from buffer since it doesn't exist in current class
if (skipPrimitiveFieldValueFailed(fory, fieldInfo.classId, buffer)) {
if (fieldInfo.classInfo == null) {
// TODO(chaokunyang) support registered serializer in peer with ref tracking disabled.
fory.readRef(buffer, classInfoHolder);
} else {
AbstractObjectSerializer.readFinalObjectFieldValue(
binding, refResolver, classResolver, fieldInfo, isFinal, buffer);
if (fieldInfo.fieldConverter == null) {
// Skip the field value from buffer since it doesn't exist in current class
if (skipPrimitiveFieldValueFailed(fory, fieldInfo.classId, buffer)) {
if (fieldInfo.classInfo == null) {
// TODO(chaokunyang) support registered serializer in peer with ref tracking disabled.
fory.readRef(buffer, classInfoHolder);
} else {
AbstractObjectSerializer.readFinalObjectFieldValue(
binding, refResolver, classResolver, fieldInfo, isFinal, buffer);
}
}
} else {
compatibleRead(buffer, fieldInfo, isFinal, obj);
}
}
}
Expand All @@ -228,20 +232,32 @@ public T read(MemoryBuffer buffer) {
fieldAccessor.putObject(obj, fieldValue);
}
}
return obj;
}

// Set default values for missing fields in Scala case classes
if (hasDefaultValues) {
DefaultValueUtils.setDefaultValues(obj, defaultValueFields);
private void compatibleRead(
MemoryBuffer buffer, FinalTypeField fieldInfo, boolean isFinal, Object obj) {
Object fieldValue;
short classId = fieldInfo.classId;
if (classId >= ClassResolver.PRIMITIVE_BOOLEAN_CLASS_ID
&& classId <= ClassResolver.PRIMITIVE_DOUBLE_CLASS_ID) {
fieldValue = Serializers.readPrimitiveValue(fory, buffer, classId);
} else {
fieldValue =
AbstractObjectSerializer.readFinalObjectFieldValue(
binding, refResolver, classResolver, fieldInfo, isFinal, buffer);
}

return obj;
fieldInfo.fieldConverter.set(obj, fieldValue);
}

private T newInstance() {
if (!hasDefaultValues) {
return newBean();
}
return Platform.newInstance(type);
T obj = Platform.newInstance(type);
// Set default values for missing fields in Scala case classes
DefaultValueUtils.setDefaultValues(obj, defaultValueFields);
return obj;
}

@Override
Expand Down Expand Up @@ -284,6 +300,7 @@ private void readFields(MemoryBuffer buffer, Object[] fields) {
binding, refResolver, classResolver, fieldInfo, isFinal, buffer);
}
}
// remapping will handle those extra fields from peers.
fields[counter++] = null;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* 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.fory.serializer.converter;

import java.lang.reflect.Field;
import org.apache.fory.reflect.FieldAccessor;

/**
* Abstract base class for field converters that handle type conversions during
* serialization/deserialization.
*
* <p>Field converters are responsible for converting values from one type to another when setting
* field values. This is particularly useful when dealing with cross-language serialization where
* type mappings may differ between languages, or when handling legacy data with different type
* representations.
*
* <p>Each converter is associated with a specific field through a {@link FieldAccessor}, which
* provides the mechanism to actually set the converted value on the target object.
*
* <p>Example usage:
*
* <pre>{@code
* // Create a converter for an int field
* FieldConverter<Integer> converter = new IntConverter(fieldAccessor);
*
* // Convert a string "123" to integer and set it on the target object
* converter.set(targetObject, "123");
* }</pre>
*
* @param <TO> the target type that this converter produces
* @see FieldConverters for factory methods to create specific converter instances
* @see FieldAccessor for the field access mechanism
*/
public abstract class FieldConverter<TO> {
private final FieldAccessor fieldAccessor;

/**
* Constructs a new FieldConverter with the specified field accessor.
*
* @param fieldAccessor the field accessor that will be used to set converted values on target
* objects
* @throws IllegalArgumentException if fieldAccessor is null
*/
protected FieldConverter(FieldAccessor fieldAccessor) {
this.fieldAccessor = fieldAccessor;
}

/**
* Converts the given object to the target type.
*
* <p>This method performs the actual type conversion logic. The implementation should handle null
* values appropriately and throw {@link UnsupportedOperationException} for incompatible types.
*
* @param from the object to convert
* @return the converted object of type TO
* @throws UnsupportedOperationException if the source type is not compatible with this converter
* @throws NumberFormatException if converting from String to a numeric type and the string is not
* a valid number
* @throws ArithmeticException if the numeric conversion would result in overflow or underflow
*/
public abstract TO convert(Object from);

/**
* Converts the given object and sets it on the target object's field.
*
* <p>This is a convenience method that combines conversion and field setting in one operation. It
* first converts the input object using {@link #convert(Object)}, then uses the field accessor to
* set the converted value on the target object.
*
* @param target the target object whose field will be set
* @param from the object to convert and set
* @throws UnsupportedOperationException if the source type is not compatible with this converter
* @throws NumberFormatException if converting from String to a numeric type and the string is not
* a valid number
* @throws ArithmeticException if the numeric conversion would result in overflow or underflow
* @throws IllegalArgumentException if target is null or the field accessor fails
*/
public void set(Object target, Object from) {
TO converted = convert(from);
fieldAccessor.set(target, converted);
}

public Field getField() {
return fieldAccessor.getField();
}
}
Loading
Loading