This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new 044efb8c5 feat(java): support type converters for comaptible mode
(#2641)
044efb8c5 is described below
commit 044efb8c50fe13d55240a7d413356748131972c2
Author: Shawn Yang <[email protected]>
AuthorDate: Mon Sep 22 17:17:02 2025 +0800
feat(java): support type converters for comaptible mode (#2641)
## Why?
<!-- Describe the purpose of this PR. -->
## What does this PR do?
Support type converters for comaptible mode, so the deserialization peer
can have different type for fields, and fory can convert to target type
instead of ignoring it.
## Related issues
Closes #2636
#1870
## Does this PR introduce any user-facing change?
<!--
If any user-facing interface changes, please [open an
issue](https://github.com/apache/fory/issues/new/choose) describing the
need to do so and update the document if necessary.
Delete section if not applicable.
-->
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
<!--
When the PR has an impact on performance (if you don't know whether the
PR will have an impact on performance, you can submit the PR first, and
if it will have impact on performance, the code reviewer will explain
it), be sure to attach a benchmark data here.
Delete section if not applicable.
-->
---
.../fory/builder/MetaSharedCodecBuilder.java | 19 +-
.../main/java/org/apache/fory/meta/ClassDef.java | 7 +
.../org/apache/fory/reflect/FieldAccessor.java | 37 +-
.../org/apache/fory/reflect/ReflectionUtils.java | 7 +
.../fory/serializer/AbstractObjectSerializer.java | 3 +
.../fory/serializer/MetaSharedSerializer.java | 45 +-
.../fory/serializer/converter/FieldConverter.java | 103 ++++
.../fory/serializer/converter/FieldConverters.java | 634 +++++++++++++++++++++
.../main/java/org/apache/fory/type/Descriptor.java | 11 +
.../org/apache/fory/type/DescriptorBuilder.java | 3 +
.../fory-core/native-image.properties | 12 +
.../fory/serializer/MetaSharedCompatibleTest.java | 145 +++++
12 files changed, 1004 insertions(+), 22 deletions(-)
diff --git
a/java/fory-core/src/main/java/org/apache/fory/builder/MetaSharedCodecBuilder.java
b/java/fory-core/src/main/java/org/apache/fory/builder/MetaSharedCodecBuilder.java
index 4c075d282..fbfc1e974 100644
---
a/java/fory-core/src/main/java/org/apache/fory/builder/MetaSharedCodecBuilder.java
+++
b/java/fory-core/src/main/java/org/apache/fory/builder/MetaSharedCodecBuilder.java
@@ -23,6 +23,7 @@ import static
org.apache.fory.builder.Generated.GeneratedMetaSharedSerializer.SE
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;
@@ -44,7 +45,9 @@ import org.apache.fory.serializer.MetaSharedSerializer;
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;
@@ -221,11 +224,25 @@ public class MetaSharedCodecBuilder extends
ObjectCodecBuilder {
@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);
}
diff --git a/java/fory-core/src/main/java/org/apache/fory/meta/ClassDef.java
b/java/fory-core/src/main/java/org/apache/fory/meta/ClassDef.java
index 1e5b14bc6..7a2bccdbe 100644
--- a/java/fory-core/src/main/java/org/apache/fory/meta/ClassDef.java
+++ b/java/fory-core/src/main/java/org/apache/fory/meta/ClassDef.java
@@ -58,6 +58,8 @@ import org.apache.fory.resolver.TypeResolver;
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;
@@ -276,6 +278,11 @@ public class ClassDef implements Serializable {
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 {
diff --git
a/java/fory-core/src/main/java/org/apache/fory/reflect/FieldAccessor.java
b/java/fory-core/src/main/java/org/apache/fory/reflect/FieldAccessor.java
index c13c9adb0..ca2a8bafc 100644
--- a/java/fory-core/src/main/java/org/apache/fory/reflect/FieldAccessor.java
+++ b/java/fory-core/src/main/java/org/apache/fory/reflect/FieldAccessor.java
@@ -23,11 +23,15 @@ import java.lang.invoke.MethodHandle;
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;
@@ -494,20 +498,39 @@ public abstract class FieldAccessor {
}
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
diff --git
a/java/fory-core/src/main/java/org/apache/fory/reflect/ReflectionUtils.java
b/java/fory-core/src/main/java/org/apache/fory/reflect/ReflectionUtils.java
index e346822e9..2adcbf333 100644
--- a/java/fory-core/src/main/java/org/apache/fory/reflect/ReflectionUtils.java
+++ b/java/fory-core/src/main/java/org/apache/fory/reflect/ReflectionUtils.java
@@ -34,6 +34,7 @@ import java.util.ArrayList;
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;
@@ -402,6 +403,12 @@ public class ReflectionUtils {
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());
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/AbstractObjectSerializer.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/AbstractObjectSerializer.java
index 82b53321d..cb52c5bfb 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/AbstractObjectSerializer.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/AbstractObjectSerializer.java
@@ -43,6 +43,7 @@ import org.apache.fory.resolver.ClassInfoHolder;
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;
@@ -990,6 +991,7 @@ public abstract class AbstractObjectSerializer<T> extends
Serializer<T> {
protected final short classId;
protected final String qualifiedFieldName;
protected final FieldAccessor fieldAccessor;
+ protected final FieldConverter<?> fieldConverter;
protected boolean nullable;
protected boolean trackingRef;
@@ -998,6 +1000,7 @@ public abstract class AbstractObjectSerializer<T> extends
Serializer<T> {
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()) {
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/MetaSharedSerializer.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/MetaSharedSerializer.java
index 56917ccf0..7d34dc9cc 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/MetaSharedSerializer.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/MetaSharedSerializer.java
@@ -200,15 +200,19 @@ public class MetaSharedSerializer<T> extends
AbstractObjectSerializer<T> {
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);
}
}
}
@@ -228,20 +232,32 @@ public class MetaSharedSerializer<T> extends
AbstractObjectSerializer<T> {
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
@@ -284,6 +300,7 @@ public class MetaSharedSerializer<T> extends
AbstractObjectSerializer<T> {
binding, refResolver, classResolver, fieldInfo, isFinal,
buffer);
}
}
+ // remapping will handle those extra fields from peers.
fields[counter++] = null;
}
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/converter/FieldConverter.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/converter/FieldConverter.java
new file mode 100644
index 000000000..2f7cec62d
--- /dev/null
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/converter/FieldConverter.java
@@ -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 <T> 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<T> {
+ 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 T
+ * @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 T 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) {
+ T converted = convert(from);
+ fieldAccessor.set(target, converted);
+ }
+
+ public Field getField() {
+ return fieldAccessor.getField();
+ }
+}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/converter/FieldConverters.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/converter/FieldConverters.java
new file mode 100644
index 000000000..87f7cdd5a
--- /dev/null
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/converter/FieldConverters.java
@@ -0,0 +1,634 @@
+/*
+ * 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 com.google.common.collect.ImmutableSet;
+import java.lang.reflect.Field;
+import java.util.Set;
+import org.apache.fory.reflect.FieldAccessor;
+import org.apache.fory.type.TypeUtils;
+
+/**
+ * Factory class for creating field converters that handle type conversions
between different data
+ * types. This class provides converters for primitive types and their boxed
counterparts, enabling
+ * automatic type conversion during serialization/deserialization processes.
+ */
+public class FieldConverters {
+
+ /**
+ * Creates an appropriate field converter based on the target field type and
source object type.
+ *
+ * @param from the source object type to convert from
+ * @param field the target field to convert to
+ * @return a FieldConverter instance that can handle the conversion, or null
if no compatible
+ * converter exists
+ */
+ public static FieldConverter<?> getConverter(Class<?> from, Field field) {
+ Class<?> to = field.getType();
+ from = TypeUtils.wrap(from);
+ // Handle primitive int conversions
+ if (to == int.class) {
+ if (IntConverter.compatibleTypes.contains(from)) {
+ return new IntConverter(FieldAccessor.createAccessor(field));
+ }
+ } else if (to == Integer.class) {
+ // Handle boxed Integer conversions
+ if (IntConverter.compatibleTypes.contains(from)) {
+ return new BoxedIntConverter(FieldAccessor.createAccessor(field));
+ }
+ } else if (to == boolean.class) {
+ // Handle primitive boolean conversions
+ if (BooleanConverter.compatibleTypes.contains(from)) {
+ return new BooleanConverter(FieldAccessor.createAccessor(field));
+ }
+ } else if (to == Boolean.class) {
+ // Handle boxed Boolean conversions
+ if (BooleanConverter.compatibleTypes.contains(from)) {
+ return new BoxedBooleanConverter(FieldAccessor.createAccessor(field));
+ }
+ } else if (to == byte.class) {
+ // Handle primitive byte conversions
+ if (ByteConverter.compatibleTypes.contains(from)) {
+ return new ByteConverter(FieldAccessor.createAccessor(field));
+ }
+ } else if (to == Byte.class) {
+ // Handle boxed Byte conversions
+ if (ByteConverter.compatibleTypes.contains(from)) {
+ return new BoxedByteConverter(FieldAccessor.createAccessor(field));
+ }
+ } else if (to == short.class) {
+ // Handle primitive short conversions
+ if (ShortConverter.compatibleTypes.contains(from)) {
+ return new ShortConverter(FieldAccessor.createAccessor(field));
+ }
+ } else if (to == Short.class) {
+ // Handle boxed Short conversions
+ if (ShortConverter.compatibleTypes.contains(from)) {
+ return new BoxedShortConverter(FieldAccessor.createAccessor(field));
+ }
+ } else if (to == long.class) {
+ // Handle primitive long conversions
+ if (LongConverter.compatibleTypes.contains(from)) {
+ return new LongConverter(FieldAccessor.createAccessor(field));
+ }
+ } else if (to == Long.class) {
+ // Handle boxed Long conversions
+ if (LongConverter.compatibleTypes.contains(from)) {
+ return new BoxedLongConverter(FieldAccessor.createAccessor(field));
+ }
+ } else if (to == float.class) {
+ // Handle primitive float conversions
+ if (FloatConverter.compatibleTypes.contains(from)) {
+ return new FloatConverter(FieldAccessor.createAccessor(field));
+ }
+ } else if (to == Float.class) {
+ // Handle boxed Float conversions
+ if (FloatConverter.compatibleTypes.contains(from)) {
+ return new BoxedFloatConverter(FieldAccessor.createAccessor(field));
+ }
+ } else if (to == double.class) {
+ // Handle primitive double conversions
+ if (DoubleConverter.compatibleTypes.contains(from)) {
+ return new DoubleConverter(FieldAccessor.createAccessor(field));
+ }
+ } else if (to == Double.class) {
+ // Handle boxed Double conversions
+ if (DoubleConverter.compatibleTypes.contains(from)) {
+ return new BoxedDoubleConverter(FieldAccessor.createAccessor(field));
+ }
+ } else if (to == String.class) {
+ // Handle String conversions
+ if (StringConverter.compatibleTypes.contains(from)) {
+ return new StringConverter(FieldAccessor.createAccessor(field));
+ }
+ }
+
+ return null; // No compatible converter found
+ }
+
+ /**
+ * Converter for primitive boolean fields. Converts compatible types to
boolean values. Returns
+ * false for null values and incompatible types.
+ */
+ public static class BooleanConverter extends FieldConverter<Boolean> {
+ static Set<Class<?>> compatibleTypes = ImmutableSet.of(String.class,
Boolean.class);
+
+ protected BooleanConverter(FieldAccessor fieldAccessor) {
+ super(fieldAccessor);
+ }
+
+ /**
+ * Converts an object to a boolean value.
+ *
+ * @param from the object to convert
+ * @return the converted boolean value
+ */
+ public static Boolean convertFrom(Object from) {
+ if (from == null) {
+ return false;
+ }
+ if (from instanceof Boolean) {
+ return (Boolean) from;
+ } else if (from instanceof String) {
+ return Boolean.parseBoolean((String) from);
+ } else {
+ throw new UnsupportedOperationException("Incompatible type: " +
from.getClass());
+ }
+ }
+
+ @Override
+ public Boolean convert(Object from) {
+ return convertFrom(from);
+ }
+ }
+
+ /**
+ * Converter for boxed Boolean fields. Converts compatible types to Boolean
values. Returns null
+ * for null values, unlike the primitive version.
+ */
+ public static class BoxedBooleanConverter extends FieldConverter<Boolean> {
+ protected BoxedBooleanConverter(FieldAccessor fieldAccessor) {
+ super(fieldAccessor);
+ }
+
+ /**
+ * Converts an object to a Boolean value.
+ *
+ * @param from the object to convert
+ * @return the converted Boolean value, or null if from is null
+ */
+ public static Boolean convertFrom(Object from) {
+ if (from == null) {
+ return null;
+ }
+ return BooleanConverter.convertFrom(from);
+ }
+
+ @Override
+ public Boolean convert(Object from) {
+ return convertFrom(from);
+ }
+ }
+
+ /**
+ * Converter for primitive byte fields. Converts compatible types to byte
values. Returns 0 for
+ * null values.
+ */
+ public static class ByteConverter extends FieldConverter<Byte> {
+ static Set<Class<?>> compatibleTypes =
+ ImmutableSet.of(String.class, Integer.class, Long.class, Short.class,
Byte.class);
+
+ protected ByteConverter(FieldAccessor fieldAccessor) {
+ super(fieldAccessor);
+ }
+
+ /**
+ * Converts an object to a byte value.
+ *
+ * @param from the object to convert
+ * @return the converted byte value
+ * @throws NumberFormatException if the string cannot be parsed as a byte
+ * @throws ArithmeticException if the numeric value is out of byte range
+ */
+ public static Byte convertFrom(Object from) {
+ if (from == null) {
+ return 0;
+ }
+ if (from instanceof Byte) {
+ return (Byte) from;
+ } else if (from instanceof Integer) {
+ return ((Integer) from).byteValue();
+ } else if (from instanceof Long) {
+ return ((Long) from).byteValue();
+ } else if (from instanceof Short) {
+ return ((Short) from).byteValue();
+ } else if (from instanceof String) {
+ return Byte.parseByte((String) from);
+ } else {
+ throw new UnsupportedOperationException("Incompatible type: " +
from.getClass());
+ }
+ }
+
+ @Override
+ public Byte convert(Object from) {
+ return convertFrom(from);
+ }
+ }
+
+ /**
+ * Converter for boxed Byte fields. Converts compatible types to Byte
values. Returns null for
+ * null values, unlike the primitive version.
+ */
+ public static class BoxedByteConverter extends FieldConverter<Byte> {
+ protected BoxedByteConverter(FieldAccessor fieldAccessor) {
+ super(fieldAccessor);
+ }
+
+ /**
+ * Converts an object to a Byte value.
+ *
+ * @param from the object to convert
+ * @return the converted Byte value, or null if from is null
+ */
+ public static Byte convertFrom(Object from) {
+ if (from == null) {
+ return null;
+ }
+ return ByteConverter.convertFrom(from);
+ }
+
+ @Override
+ public Byte convert(Object from) {
+ return convertFrom(from);
+ }
+ }
+
+ /**
+ * Converter for primitive short fields. Converts compatible types to short
values. Returns 0 for
+ * null values.
+ */
+ public static class ShortConverter extends FieldConverter<Short> {
+ static Set<Class<?>> compatibleTypes =
+ ImmutableSet.of(String.class, Integer.class, Long.class, Short.class);
+
+ protected ShortConverter(FieldAccessor fieldAccessor) {
+ super(fieldAccessor);
+ }
+
+ /**
+ * Converts an object to a short value.
+ *
+ * @param from the object to convert
+ * @return the converted short value
+ * @throws NumberFormatException if the string cannot be parsed as a short
+ * @throws ArithmeticException if the numeric value is out of short range
+ */
+ public static Short convertFrom(Object from) {
+ if (from == null) {
+ return 0;
+ }
+ if (from instanceof Short) {
+ return (Short) from;
+ } else if (from instanceof Integer) {
+ return ((Integer) from).shortValue();
+ } else if (from instanceof Long) {
+ return ((Long) from).shortValue();
+ } else if (from instanceof String) {
+ return Short.parseShort((String) from);
+ } else {
+ throw new UnsupportedOperationException("Incompatible type: " +
from.getClass());
+ }
+ }
+
+ @Override
+ public Short convert(Object from) {
+ return convertFrom(from);
+ }
+ }
+
+ /**
+ * Converter for boxed Short fields. Converts compatible types to Short
values. Returns null for
+ * null values, unlike the primitive version.
+ */
+ public static class BoxedShortConverter extends FieldConverter<Short> {
+ protected BoxedShortConverter(FieldAccessor fieldAccessor) {
+ super(fieldAccessor);
+ }
+
+ public static Short convertFrom(Object from) {
+ if (from == null) {
+ return null;
+ }
+ return ShortConverter.convertFrom(from);
+ }
+
+ @Override
+ public Short convert(Object from) {
+ return convertFrom(from);
+ }
+ }
+
+ /**
+ * Converter for primitive int fields. Converts compatible types to int
values. Returns 0 for null
+ * values.
+ */
+ public static class IntConverter extends FieldConverter<Integer> {
+ static Set<Class<?>> compatibleTypes = ImmutableSet.of(String.class,
Long.class, Integer.class);
+
+ protected IntConverter(FieldAccessor fieldAccessor) {
+ super(fieldAccessor);
+ }
+
+ /**
+ * Converts an object to an int value.
+ *
+ * @param from the object to convert
+ * @return the converted int value
+ * @throws NumberFormatException if the string cannot be parsed as an int
+ * @throws ArithmeticException if the numeric value is out of int range
+ */
+ public static Integer convertFrom(Object from) {
+ if (from == null) {
+ return 0;
+ }
+ if (from instanceof Long) {
+ return Math.toIntExact((Long) from);
+ } else if (from instanceof Integer) {
+ return (Integer) from;
+ } else if (from instanceof String) {
+ return Integer.parseInt((String) from);
+ } else {
+ throw new UnsupportedOperationException("Incompatible type: " +
from.getClass());
+ }
+ }
+
+ @Override
+ public Integer convert(Object from) {
+ return convertFrom(from);
+ }
+ }
+
+ /**
+ * Converter for boxed Integer fields. Converts compatible types to Integer
values. Returns null
+ * for null values, unlike the primitive version.
+ */
+ public static class BoxedIntConverter extends FieldConverter<Integer> {
+ protected BoxedIntConverter(FieldAccessor fieldAccessor) {
+ super(fieldAccessor);
+ }
+
+ /**
+ * Converts an object to an Integer value.
+ *
+ * @param from the object to convert
+ * @return the converted Integer value, or null if from is null
+ */
+ public static Integer convertFrom(Object from) {
+ if (from == null) {
+ return null;
+ }
+ return IntConverter.convertFrom(from);
+ }
+
+ @Override
+ public Integer convert(Object from) {
+ return convertFrom(from);
+ }
+ }
+
+ /**
+ * Converter for primitive long fields. Converts compatible types to long
values. Returns 0 for
+ * null values.
+ */
+ public static class LongConverter extends FieldConverter<Long> {
+ static Set<Class<?>> compatibleTypes = ImmutableSet.of(String.class,
Long.class);
+
+ protected LongConverter(FieldAccessor fieldAccessor) {
+ super(fieldAccessor);
+ }
+
+ /**
+ * Converts an object to a long value.
+ *
+ * @param from the object to convert
+ * @return the converted long value
+ * @throws NumberFormatException if the string cannot be parsed as a long
+ */
+ public static Long convertFrom(Object from) {
+ if (from == null) {
+ return 0L;
+ }
+ if (from instanceof Long) {
+ return (Long) from;
+ } else if (from instanceof String) {
+ return Long.parseLong((String) from);
+ } else {
+ throw new UnsupportedOperationException("Incompatible type: " +
from.getClass());
+ }
+ }
+
+ @Override
+ public Long convert(Object from) {
+ return convertFrom(from);
+ }
+ }
+
+ /**
+ * Converter for boxed Long fields. Converts compatible types to Long
values. Returns null for
+ * null values, unlike the primitive version.
+ */
+ public static class BoxedLongConverter extends FieldConverter<Long> {
+ protected BoxedLongConverter(FieldAccessor fieldAccessor) {
+ super(fieldAccessor);
+ }
+
+ /**
+ * Converts an object to a Long value.
+ *
+ * @param from the object to convert
+ * @return the converted Long value, or null if from is null
+ */
+ public static Long convertFrom(Object from) {
+ if (from == null) {
+ return null;
+ }
+ return LongConverter.convertFrom(from);
+ }
+
+ @Override
+ public Long convert(Object from) {
+ return convertFrom(from);
+ }
+ }
+
+ /**
+ * Converter for primitive float fields. Converts compatible types to float
values. Returns 0.0f
+ * for null values. Only allows conversion from String.
+ */
+ public static class FloatConverter extends FieldConverter<Float> {
+ static Set<Class<?>> compatibleTypes = ImmutableSet.of(String.class,
Float.class);
+
+ protected FloatConverter(FieldAccessor fieldAccessor) {
+ super(fieldAccessor);
+ }
+
+ /**
+ * Converts an object to a float value.
+ *
+ * @param from the object to convert
+ * @return the converted float value
+ * @throws NumberFormatException if the string cannot be parsed as a float
+ */
+ public static Float convertFrom(Object from) {
+ if (from == null) {
+ return 0.0f;
+ }
+ if (from instanceof String) {
+ return Float.parseFloat((String) from);
+ } else if (from instanceof Float) {
+ return (Float) from;
+ } else {
+ throw new UnsupportedOperationException("Incompatible type: " +
from.getClass());
+ }
+ }
+
+ @Override
+ public Float convert(Object from) {
+ return convertFrom(from);
+ }
+ }
+
+ /**
+ * Converter for boxed Float fields. Converts compatible types to Float
values. Returns null for
+ * null values, unlike the primitive version. Only allows conversion from
String.
+ */
+ public static class BoxedFloatConverter extends FieldConverter<Float> {
+ protected BoxedFloatConverter(FieldAccessor fieldAccessor) {
+ super(fieldAccessor);
+ }
+
+ /**
+ * Converts an object to a Float value.
+ *
+ * @param from the object to convert
+ * @return the converted Float value, or null if from is null
+ */
+ public static Float convertFrom(Object from) {
+ if (from == null) {
+ return null;
+ }
+ return FloatConverter.convertFrom(from);
+ }
+
+ @Override
+ public Float convert(Object from) {
+ return convertFrom(from);
+ }
+ }
+
+ /**
+ * Converter for primitive double fields. Converts compatible types to
double values. Returns 0.0
+ * for null values. Allows conversion from String and Float.
+ */
+ public static class DoubleConverter extends FieldConverter<Double> {
+ static Set<Class<?>> compatibleTypes = ImmutableSet.of(String.class,
Float.class, Double.class);
+
+ protected DoubleConverter(FieldAccessor fieldAccessor) {
+ super(fieldAccessor);
+ }
+
+ /**
+ * Converts an object to a double value.
+ *
+ * @param from the object to convert
+ * @return the converted double value
+ * @throws NumberFormatException if the string cannot be parsed as a double
+ */
+ public static Double convertFrom(Object from) {
+ if (from == null) {
+ return 0.0;
+ }
+ if (from instanceof String) {
+ return Double.parseDouble((String) from);
+ } else if (from instanceof Double) {
+ return (Double) from;
+ } else if (from instanceof Float) {
+ return ((Float) from).doubleValue();
+ } else {
+ throw new UnsupportedOperationException("Incompatible type: " +
from.getClass());
+ }
+ }
+
+ @Override
+ public Double convert(Object from) {
+ return convertFrom(from);
+ }
+ }
+
+ /**
+ * Converter for boxed Double fields. Converts compatible types to Double
values. Returns null for
+ * null values, unlike the primitive version. Allows conversion from String
and Float.
+ */
+ public static class BoxedDoubleConverter extends FieldConverter<Double> {
+ protected BoxedDoubleConverter(FieldAccessor fieldAccessor) {
+ super(fieldAccessor);
+ }
+
+ /**
+ * Converts an object to a Double value.
+ *
+ * @param from the object to convert
+ * @return the converted Double value, or null if from is null
+ */
+ public static Double convertFrom(Object from) {
+ if (from == null) {
+ return null;
+ }
+ return DoubleConverter.convertFrom(from);
+ }
+
+ @Override
+ public Double convert(Object from) {
+ return convertFrom(from);
+ }
+ }
+
+ /**
+ * Converter for String fields. Converts compatible types to String values.
Only allows conversion
+ * from Number types to prevent malicious toString() calls.
+ */
+ public static class StringConverter extends FieldConverter<String> {
+ static Set<Class<?>> compatibleTypes =
+ ImmutableSet.of(
+ Integer.class,
+ Long.class,
+ Short.class,
+ Byte.class,
+ Boolean.class,
+ Float.class,
+ Double.class);
+
+ protected StringConverter(FieldAccessor fieldAccessor) {
+ super(fieldAccessor);
+ }
+
+ /**
+ * Converts an object to a String value.
+ *
+ * @param from the object to convert
+ * @return the converted String value, or null if from is null
+ */
+ public static String convertFrom(Object from) {
+ if (from == null) {
+ return null;
+ } else if (from instanceof Number || from instanceof Boolean) {
+ return from.toString();
+ } else {
+ // disallow on other types, to avoid malicious toString get called.
+ throw new UnsupportedOperationException("Incompatible type: " +
from.getClass());
+ }
+ }
+
+ @Override
+ public String convert(Object from) {
+ return convertFrom(from);
+ }
+ }
+}
diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java
b/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java
index 06d09a5d5..decb2b93a 100644
--- a/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java
+++ b/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java
@@ -50,6 +50,7 @@ import org.apache.fory.collection.Collections;
import org.apache.fory.collection.Tuple2;
import org.apache.fory.memory.Platform;
import org.apache.fory.reflect.TypeRef;
+import org.apache.fory.serializer.converter.FieldConverter;
import org.apache.fory.util.Preconditions;
import org.apache.fory.util.StringUtils;
import org.apache.fory.util.record.RecordComponent;
@@ -89,6 +90,7 @@ public class Descriptor {
private ForyField foryField;
private boolean nullable;
private boolean trackingRef;
+ private FieldConverter<?> fieldConverter;
public Descriptor(Field field, TypeRef<?> typeRef, Method readMethod, Method
writeMethod) {
this.field = field;
@@ -185,6 +187,7 @@ public class Descriptor {
this.trackingRef = builder.trackingRef;
this.type = builder.type;
this.foryField = builder.foryField;
+ this.fieldConverter = builder.fieldConverter;
}
public DescriptorBuilder copyBuilder() {
@@ -279,6 +282,14 @@ public class Descriptor {
return typeRef;
}
+ public FieldConverter<?> getFieldConverter() {
+ return fieldConverter;
+ }
+
+ public void setFieldConverter(FieldConverter<?> fieldConverter) {
+ this.fieldConverter = fieldConverter;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Descriptor{");
diff --git
a/java/fory-core/src/main/java/org/apache/fory/type/DescriptorBuilder.java
b/java/fory-core/src/main/java/org/apache/fory/type/DescriptorBuilder.java
index 643ab5a94..6e75c4966 100644
--- a/java/fory-core/src/main/java/org/apache/fory/type/DescriptorBuilder.java
+++ b/java/fory-core/src/main/java/org/apache/fory/type/DescriptorBuilder.java
@@ -23,6 +23,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.apache.fory.annotation.ForyField;
import org.apache.fory.reflect.TypeRef;
+import org.apache.fory.serializer.converter.FieldConverter;
public class DescriptorBuilder {
@@ -38,6 +39,7 @@ public class DescriptorBuilder {
ForyField foryField;
boolean nullable;
boolean trackingRef;
+ FieldConverter fieldConverter;
public DescriptorBuilder(Descriptor descriptor) {
this.typeRef = descriptor.getTypeRef();
@@ -52,6 +54,7 @@ public class DescriptorBuilder {
this.foryField = descriptor.getForyField();
this.nullable = descriptor.isNullable();
this.trackingRef = descriptor.isTrackingRef();
+ this.fieldConverter = descriptor.getFieldConverter();
}
public DescriptorBuilder typeRef(TypeRef<?> typeRef) {
diff --git
a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
index 2e908e419..8796e6db0 100644
---
a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
+++
b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
@@ -439,6 +439,18 @@
Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\
org.apache.fory.serializer.LazySerializer$LazyObjectSerializer,\
org.apache.fory.serializer.shim.ShimDispatcher,\
org.apache.fory.serializer.shim.ProtobufDispatcher,\
+ org.apache.fory.serializer.converter.FieldConverter,\
+ org.apache.fory.serializer.converter.FieldConverters$BooleanConverter,\
+
org.apache.fory.serializer.converter.FieldConverters$BoxedBooleanConverter,\
+ org.apache.fory.serializer.converter.FieldConverters$ByteConverter,\
+ org.apache.fory.serializer.converter.FieldConverters$BoxedByteConverter,\
+ org.apache.fory.serializer.converter.FieldConverters$ShortConverter,\
+ org.apache.fory.serializer.converter.FieldConverters$BoxedShortConverter,\
+ org.apache.fory.serializer.converter.FieldConverters$IntConverter,\
+ org.apache.fory.serializer.converter.FieldConverters$BoxedIntConverter,\
+ org.apache.fory.serializer.converter.FieldConverters$LongConverter,\
+ org.apache.fory.serializer.converter.FieldConverters$BoxedLongConverter,\
+ org.apache.fory.serializer.converter.FieldConverters$StringConverter,\
org.apache.fory.serializer.ObjectStreamSerializer,\
org.apache.fory.serializer.ObjectStreamSerializer$1,\
org.apache.fory.serializer.ObjectStreamSerializer$StreamClassInfo,\
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/MetaSharedCompatibleTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/MetaSharedCompatibleTest.java
index 9df8ca47b..eecb7aa7d 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/MetaSharedCompatibleTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/MetaSharedCompatibleTest.java
@@ -41,6 +41,8 @@ import org.apache.fory.meta.ClassDefEncoderTest;
import org.apache.fory.reflect.ReflectionUtils;
import org.apache.fory.resolver.MetaContext;
import org.apache.fory.serializer.collection.UnmodifiableSerializersTest;
+import org.apache.fory.serializer.converter.FieldConverter;
+import org.apache.fory.serializer.converter.FieldConverters;
import org.apache.fory.test.bean.BeanA;
import org.apache.fory.test.bean.BeanB;
import org.apache.fory.test.bean.CollectionFields;
@@ -939,4 +941,147 @@ public class MetaSharedCompatibleTest extends
ForyTestBase {
Assert.assertEquals(ReflectionUtils.getObjectFieldValue(o1, "f2"), 20);
Assert.assertEquals(ReflectionUtils.getObjectFieldValue(o1, "f3"), 30);
}
+
+ @Test
+ public void testCompatibleFieldConvert() throws Exception {
+ byte[] bytes;
+ Object o1;
+ ImmutableSet<String> floatFields = ImmutableSet.of("f11", "f12", "f13",
"f14");
+ {
+ CompileUnit compileUnit =
+ new CompileUnit(
+ "",
+ "CompatibleFieldConvert",
+ ("public final class CompatibleFieldConvert {\n"
+ + " public boolean ftrue;\n"
+ + " public Boolean ffalse;\n"
+ + " public byte f3;\n"
+ + " public Byte f4;\n"
+ + " public short f5;\n"
+ + " public Short f6;\n"
+ + " public int f7;\n"
+ + " public Integer f8;\n"
+ + " public long f9;\n"
+ + " public Long f10;\n"
+ + " public float f11;\n"
+ + " public Float f12;\n"
+ + " public double f13;\n"
+ + " public Double f14;\n"
+ + " public String toString() {return \"\" + ftrue + ffalse
+ "
+ + "f3 + f4 + f5 + f6 + f7 + f8 + f9 + f10 + f11 + f12 + f13
+ f14;}\n"
+ + "}"));
+
+ ClassLoader classLoader =
+ JaninoUtils.compile(Thread.currentThread().getContextClassLoader(),
compileUnit);
+ Class<?> cls =
classLoader.loadClass(compileUnit.getQualifiedClassName());
+ o1 = cls.newInstance();
+ for (Field field : ReflectionUtils.getSortedFields(cls, false)) {
+ String name = field.getName();
+ field.setAccessible(true);
+ FieldConverter<?> converter =
FieldConverters.getConverter(String.class, field);
+ Assert.assertNotNull(converter);
+ Object converted = converter.convert(name.substring(1));
+ field.set(o1, converted);
+ }
+ Fory fory =
+ builder()
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .withClassLoader(classLoader)
+ .build();
+ bytes = fory.serialize(o1);
+ }
+ {
+ CompileUnit compileUnit =
+ new CompileUnit(
+ "",
+ "CompatibleFieldConvert",
+ ("public final class CompatibleFieldConvert {\n"
+ + " public Boolean ftrue;\n"
+ + " public boolean ffalse;\n"
+ + " public Byte f3;\n"
+ + " public byte f4;\n"
+ + " public Short f5;\n"
+ + " public short f6;\n"
+ + " public Integer f7;\n"
+ + " public int f8;\n"
+ + " public Long f9;\n"
+ + " public long f10;\n"
+ + " public Float f11;\n"
+ + " public float f12;\n"
+ + " public Double f13;\n"
+ + " public double f14;\n"
+ + " public String toString() {return \"\" + ftrue + ffalse
+ "
+ + "f3 + f4 + f5 + f6 + f7 + f8 + f9 + f10 + f11 + f12 + f13
+ f14;}\n"
+ + "}"));
+ ClassLoader classLoader =
+ JaninoUtils.compile(Thread.currentThread().getContextClassLoader(),
compileUnit);
+ Class<?> cls =
classLoader.loadClass(compileUnit.getQualifiedClassName());
+ Assert.assertNotEquals(cls, o1.getClass());
+ Fory fory =
+ builder()
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .withClassLoader(classLoader)
+ .build();
+ Object o = fory.deserialize(bytes);
+ Assert.assertEquals(o.getClass(), cls);
+ List<Field> fields = ReflectionUtils.getSortedFields(cls, false);
+ for (Field field : fields) {
+ field.setAccessible(true);
+ Object fieldValue = field.get(o);
+ if (fieldValue instanceof Float || fieldValue instanceof Double) {
+ Assert.assertEquals(fieldValue.toString(),
field.getName().substring(1) + ".0");
+ } else {
+ Assert.assertEquals(fieldValue.toString(),
field.getName().substring(1));
+ }
+ }
+ Assert.assertEquals(o.toString(), o1.toString());
+ }
+ {
+ CompileUnit compileUnit =
+ new CompileUnit(
+ "",
+ "CompatibleFieldConvert",
+ ("public final class CompatibleFieldConvert {\n"
+ + " public String ftrue;\n"
+ + " public String ffalse;\n"
+ + " public String f3;\n"
+ + " public String f4;\n"
+ + " public String f5;\n"
+ + " public String f6;\n"
+ + " public String f7;\n"
+ + " public String f8;\n"
+ + " public String f9;\n"
+ + " public String f10;\n"
+ + " public String f11;\n"
+ + " public String f12;\n"
+ + " public String f13;\n"
+ + " public String f14;\n"
+ + " public String toString() {return \"\" + ftrue + ffalse
+ "
+ + "f3 + f4 + f5 + f6 + f7 + f8 + f9 + f10 + f11 + f12 + f13
+ f14;}\n"
+ + "}"));
+
+ ClassLoader classLoader =
+ JaninoUtils.compile(Thread.currentThread().getContextClassLoader(),
compileUnit);
+ Fory fory =
+ builder()
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .withClassLoader(classLoader)
+ .build();
+ Class<?> cls =
classLoader.loadClass(compileUnit.getQualifiedClassName());
+ Assert.assertNotEquals(cls, o1.getClass());
+ Object o = fory.deserialize(bytes);
+ Assert.assertEquals(o.getClass(), cls);
+ List<Field> fields = ReflectionUtils.getSortedFields(cls, false);
+ for (Field field : fields) {
+ field.setAccessible(true);
+ Object fieldValue = field.get(o);
+ if (floatFields.contains(field.getName())) {
+ Assert.assertEquals(fieldValue.toString(),
field.getName().substring(1) + ".0");
+ } else {
+ Assert.assertEquals(fieldValue.toString(),
field.getName().substring(1));
+ }
+ }
+ Assert.assertEquals(o.toString(), o1.toString());
+ }
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]