net.openhft.chronicle.wire.WireMarshaller Maven / Gradle / Ivy
/*
* Copyright 2016-2020 chronicle.software
*
* https://chronicle.software
*
* Licensed 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 net.openhft.chronicle.wire;
import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.bytes.HexDumpBytesDescription;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.Maths;
import net.openhft.chronicle.core.OS;
import net.openhft.chronicle.core.UnsafeMemory;
import net.openhft.chronicle.core.io.*;
import net.openhft.chronicle.core.scoped.ScopedResource;
import net.openhft.chronicle.core.util.ClassLocal;
import net.openhft.chronicle.core.util.ClassNotFoundRuntimeException;
import net.openhft.chronicle.core.util.ObjectUtils;
import net.openhft.chronicle.core.util.StringUtils;
import net.openhft.chronicle.core.values.IntValue;
import net.openhft.chronicle.core.values.LongValue;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static net.openhft.chronicle.core.UnsafeMemory.*;
/**
* The WireMarshaller class is responsible for marshalling and unmarshalling of objects of type T.
* This class provides an efficient mechanism for serialization/deserialization using wire protocols.
* It utilizes field accessors to read and write values directly to and from the fields of the object.
*
* @param The type of the object to be marshalled/unmarshalled.
*/
@SuppressWarnings({"rawtypes", "unchecked", "deprecation"})
public class WireMarshaller {
private static final Class[] UNEXPECTED_FIELDS_PARAMETER_TYPES = {Object.class, ValueIn.class};
private static final FieldAccess[] NO_FIELDS = {};
private static Method isRecord;
@NotNull
final FieldAccess[] fields;
// Map for quick field look-up based on their names.
final TreeMap fieldMap = new TreeMap<>(WireMarshaller::compare);
// Flag to determine if this marshaller is for a leaf class.
private final boolean isLeaf;
// Default value for the type T.
@Nullable
private final T defaultValue;
static {
if (Jvm.isJava14Plus()) {
try {
isRecord = Jvm.getMethod(Class.class, "isRecord");
} catch (Exception ignored) {
}
}
}
/**
* Constructs a new instance of the WireMarshaller with the specified parameters.
*
* @param tClass The class of the object to be marshalled.
* @param fields An array of field accessors that provide access to the fields of the object.
* @param isLeaf Indicates if the marshaller is for a leaf class.
*/
protected WireMarshaller(@NotNull Class tClass, @NotNull FieldAccess[] fields, boolean isLeaf) {
this(fields, isLeaf, defaultValueForType(tClass));
}
// A class-local storage for caching WireMarshallers for different types.
// Depending on the type of class, it either creates a marshaller for exceptions or a generic one.
public static final ClassLocal WIRE_MARSHALLER_CL = ClassLocal.withInitial
(tClass ->
Throwable.class.isAssignableFrom(tClass)
? WireMarshaller.ofThrowable(tClass)
: WireMarshaller.of(tClass)
);
WireMarshaller(@NotNull FieldAccess[] fields, boolean isLeaf, @Nullable T defaultValue) {
this.fields = fields;
this.isLeaf = isLeaf;
this.defaultValue = defaultValue;
for (FieldAccess field : fields) {
fieldMap.put(field.key.name(), field);
}
}
/**
* Factory method to create an instance of the WireMarshaller for a specific class type.
* Determines the appropriate marshaller type (basic or one that handles unexpected fields)
* based on the characteristics of the provided class.
*
* @param tClass The class type for which the marshaller is to be created.
* @return A new instance of WireMarshaller for the provided class type.
*/
@NotNull
public static WireMarshaller of(@NotNull Class tClass) {
if (tClass.isInterface() || (tClass.isEnum() && !DynamicEnum.class.isAssignableFrom(tClass)))
return new WireMarshaller<>(tClass, NO_FIELDS, true);
T defaultObject = defaultValueForType(tClass);
@NotNull Map map = new LinkedHashMap<>();
getAllField(tClass, map);
final FieldAccess[] fields = map.values().stream()
// for Java 15+ strip "hidden" fields that can't be accessed in Java 15+ this way.
.filter(field -> !(Jvm.isJava15Plus() && field.getName().matches("^.*\\$\\d+$")))
.map(field -> FieldAccess.create(field, defaultObject))
.toArray(FieldAccess[]::new);
Map fieldCount = Stream.of(fields).collect(Collectors.groupingBy(f -> f.field.getName(), Collectors.counting()));
fieldCount.forEach((n, c) -> {
if (c > 1) Jvm.warn().on(tClass, "Has " + c + " fields called '" + n + "'");
});
List collect = Stream.of(fields)
.filter(WireMarshaller::leafable)
.collect(Collectors.toList());
boolean isLeaf = collect.isEmpty();
return overridesUnexpectedFields(tClass)
? new WireMarshallerForUnexpectedFields<>(fields, isLeaf, defaultObject)
: new WireMarshaller<>(fields, isLeaf, defaultObject);
}
/**
* Checks if the provided field accessor corresponds to a "leaf" entity.
* An entity is considered a leaf if it doesn't need to be further broken down in the serialization process.
*
* @param c The field accessor to be checked.
* @return {@code true} if the field accessor is leafable, {@code false} otherwise.
*/
protected static boolean leafable(FieldAccess c) {
Class> type = c.field.getType();
if (isCollection(type))
return !Boolean.TRUE.equals(c.isLeaf);
if (DynamicEnum.class.isAssignableFrom(type))
return false;
return WriteMarshallable.class.isAssignableFrom(type);
}
/**
* Determines if the provided class overrides the "unexpectedField" method from the ReadMarshallable interface.
*
* @param tClass The class type to be checked.
* @return {@code true} if the class overrides the "unexpectedField" method, {@code false} otherwise.
*/
private static boolean overridesUnexpectedFields(Class tClass) {
try {
Method method = tClass.getMethod("unexpectedField", UNEXPECTED_FIELDS_PARAMETER_TYPES);
return method.getDeclaringClass() != ReadMarshallable.class;
} catch (NoSuchMethodException e) {
return false;
}
}
/**
* Factory method to create an instance of the WireMarshaller for a Throwable class type.
* The method identifies fields that should be marshalled and prepares the marshaller accordingly.
*
* @param tClass The Throwable class type for which the marshaller is to be created.
* @return A new instance of WireMarshaller for the provided Throwable class type.
*/
@NotNull
private static WireMarshaller ofThrowable(@NotNull Class tClass) {
T defaultObject = defaultValueForType(tClass);
@NotNull Map map = new LinkedHashMap<>();
getAllField(tClass, map);
final FieldAccess[] fields = map.values().stream()
.map(field -> FieldAccess.create(field, defaultObject)).toArray(FieldAccess[]::new);
boolean isLeaf = false;
return new WireMarshaller<>(fields, isLeaf, defaultObject);
}
/**
* Determines if the provided class is a collection type, including arrays, standard Collections, or Maps.
*
* @param c The class to be checked.
* @return {@code true} if the class is a collection type, {@code false} otherwise.
*/
private static boolean isCollection(@NotNull Class> c) {
return c.isArray() ||
Collection.class.isAssignableFrom(c) ||
Map.class.isAssignableFrom(c);
}
/**
* Recursively fetches all non-static, non-transient fields from the provided class and its superclasses,
* up to but not including Object or AbstractCommonMarshallable, and adds them to the provided map.
* Fields that are flagged for exclusion (e.g., the "ordinal" field for Enum types) are skipped.
*
* @param clazz The class type from which fields are to be extracted.
* @param map The map to populate with field names and their corresponding Field objects.
*/
public static void getAllField(@NotNull Class> clazz, @NotNull Map map) {
if (clazz != Object.class && clazz != AbstractCommonMarshallable.class)
getAllField(clazz.getSuperclass(), map);
for (@NotNull Field field : clazz.getDeclaredFields()) {
if ((field.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0)
continue;
String fieldName = field.getName();
if (("ordinal".equals(fieldName) || "hash".equals(fieldName)) && Enum.class.isAssignableFrom(clazz))
continue;
String name = fieldName;
if (name.startsWith("this$0")) {
if (ValidatableUtil.validateEnabled())
Jvm.warn().on(WireMarshaller.class, "Found " + name + ", in " + clazz + " which will be ignored!");
continue;
}
Jvm.setAccessible(field);
map.put(name, field);
}
}
static T defaultValueForType(@NotNull Class tClass) {
// tClass = ObjectUtils.implementationToUse(tClass);
if (ObjectUtils.isConcreteClass(tClass)
&& !tClass.getName().startsWith("java")
&& !tClass.isEnum()
&& !tClass.isArray()) {
T t = ObjectUtils.newInstance(tClass);
Monitorable.unmonitor(t);
return t;
}
if (DynamicEnum.class.isAssignableFrom(tClass)) {
try {
T t = OS.memory().allocateInstance(tClass);
Jvm.getField(Enum.class, "name").set(t, "[unset]");
Jvm.getField(Enum.class, "ordinal").set(t, -1);
IOTools.unmonitor(t);
return t;
} catch (InstantiationException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
return null;
}
/**
* Compares two CharSequences lexicographically. This is a character-by-character comparison
* that returns the difference of the first unmatched characters or the difference in their lengths
* if one sequence is a prefix of the other.
*
* @param cs0 The first CharSequence to be compared.
* @param cs1 The second CharSequence to be compared.
* @return A positive integer if {@code cs0} comes after {@code cs1},
* a negative integer if {@code cs0} comes before {@code cs1},
* or zero if the sequences are equal.
*/
private static int compare(CharSequence cs0, CharSequence cs1) {
for (int i = 0, len = Math.min(cs0.length(), cs1.length()); i < len; i++) {
int cmp = Character.compare(cs0.charAt(i), cs1.charAt(i));
if (cmp != 0)
return cmp;
}
return Integer.compare(cs0.length(), cs1.length());
}
/**
* Computes the actual type arguments for a given field of an interface. This method determines
* the generic type arguments that the field uses based on the interface's type parameters.
*
* @param iface The interface containing the field.
* @param field The field whose type arguments need to be determined.
* @return An array of actual type arguments or the interface's type parameters if no actual arguments can be deduced.
*/
private static Type[] computeActualTypeArguments(Class> iface, Field field) {
Type[] actual = consumeActualTypeArguments(new HashMap<>(), iface, field.getGenericType());
if (actual == null)
return iface.getTypeParameters();
return actual;
}
/**
* Determines the actual type arguments used by a class or interface for a given interface type.
* This method recursively inspects the type hierarchy to match type arguments against the
* interface's type parameters. It uses a previously built map of type parameter names to their
* actual types to deduce the correct arguments for the given interface.
*
* @param prevTypeParameters A map containing previously discovered type parameter names
* mapped to their actual types.
* @param iface The interface for which we want to determine the type arguments.
* @param type The type to inspect. This could be an actual class, interface, or
* a parameterized type that uses generic arguments.
*
* @return An array of actual type arguments used by the provided type for the specified interface,
* or null if the type doesn't directly or indirectly implement or extend the given interface.
*/
private static Type[] consumeActualTypeArguments(Map prevTypeParameters, Class> iface, Type type) {
Class> cls = null;
Map typeParameters = new HashMap<>();
// If the type is a ParameterizedType, retrieve its actual type arguments and
// map them against the declared type parameters.
if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
Type[] typeArguments = pType.getActualTypeArguments();
cls = ((Class) ((ParameterizedType) type).getRawType());
TypeVariable>[] typeParamDecls = cls.getTypeParameters();
for (int i = 0; i < Math.min(typeParamDecls.length, typeArguments.length); i++) {
Type value;
// If the actual type argument is another type variable, try to get its actual type
// from the previously discovered type parameters.
if (typeArguments[i] instanceof TypeVariable) {
value = prevTypeParameters.get(((TypeVariable>) typeArguments[i]).getName());
// Use a bound type or Object.class if the actual type isn't found.
if (value == null) {
// Fail-safe.
Type[] bounds = ((TypeVariable>) typeArguments[i]).getBounds();
value = bounds.length == 0 ? Object.class : bounds[0];
}
} else {
value = typeArguments[i];
}
typeParameters.put(typeParamDecls[i].getName(), value);
}
} else if (type instanceof Class) {
// If the type is a Class (not a ParameterizedType), directly use it.
cls = (Class) type;
}
// If the provided type (or its raw type in case of a ParameterizedType) matches the target
// interface, map the discovered type arguments to the interface's type parameters and return them.
if (iface.equals(cls)) {
TypeVariable[] parameters = iface.getTypeParameters();
Type[] result = new Type[parameters.length];
for (int i = 0; i < result.length; i++) {
Type parameter = typeParameters.get(parameters[i].getName());
result[i] = parameter != null ? parameter : parameters[i];
}
return result;
}
// Recursively inspect the generic interfaces and superclass of the type to discover
// the actual type arguments for the given interface.
if (cls != null) {
for (Type ifaceType : cls.getGenericInterfaces()) {
Type[] result = consumeActualTypeArguments(typeParameters, iface, ifaceType);
if (result != null)
return result;
}
return consumeActualTypeArguments(typeParameters, iface, cls.getGenericSuperclass());
}
return null;
}
/**
* Excludes specified fields from the current marshaller and returns a new instance of the marshaller
* with the remaining fields.
*
* @param fieldNames Names of the fields to be excluded.
* @return A new instance of the {@link WireMarshaller} with the specified fields excluded.
*/
public WireMarshaller excludeFields(String... fieldNames) {
Set fieldSet = new HashSet<>(Arrays.asList(fieldNames));
return new WireMarshaller<>(Stream.of(fields)
.filter(f -> !fieldSet.contains(f.field.getName()))
.toArray(FieldAccess[]::new),
isLeaf, defaultValue);
}
/**
* Writes the marshallable representation of the given object to the provided {@link WireOut} destination.
* This will traverse the fields and use their respective {@link FieldAccess} to write each field.
* The method also adjusts the hex dump indentation for better readability in the output.
*
* @param t The object to write.
* @param out The destination {@link WireOut} where the object representation will be written.
* @throws InvalidMarshallableException If the object fails validation checks before serialization.
*/
public void writeMarshallable(T t, @NotNull WireOut out) throws InvalidMarshallableException {
ValidatableUtil.validate(t);
HexDumpBytesDescription> bytes = out.bytesComment();
bytes.adjustHexDumpIndentation(+1);
try {
for (@NotNull FieldAccess field : fields)
field.write(t, out);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
bytes.adjustHexDumpIndentation(-1);
}
/**
* Writes the marshallable representation of the given object to the provided {@link Bytes} destination.
* Unlike the previous method, this doesn't adjust the hex dump indentation. It's a more direct
* serialization of the fields to bytes.
*
* @param t The object to write.
* @param bytes The destination {@link Bytes} where the object representation will be written.
*/
public void writeMarshallable(T t, Bytes> bytes) {
for (@NotNull FieldAccess field : fields) {
try {
field.getAsBytes(t, bytes);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
}
/**
* Writes the values of the fields from the provided object (DTO) to the output. Before writing,
* the object is validated. The method also supports optional copying of the values
* from the source object to a previous instance.
*
* @param t Object whose field values are to be written.
* @param out Output destination where the field values are written to.
* @param copy Flag indicating whether to copy values from the source object to the previous object.
* @throws InvalidMarshallableException If there's an error during marshalling.
*/
public void writeMarshallable(T t, @NotNull WireOut out, boolean copy) throws InvalidMarshallableException {
// Validate the object before writing
ValidatableUtil.validate(t);
try {
// Iterate through all fields and write their values to the output
for (@NotNull FieldAccess field : fields) {
field.write(t, out, defaultValue, copy);
}
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
/**
* Reads and populates the DTO based on the provided input. The input order can be hinted.
* After reading, the object is validated.
*
* @param t Object to populate with read values.
* @param in Input source from which values are read.
* @param overwrite Flag indicating whether to overwrite the existing value in the target object.
* @throws InvalidMarshallableException If there is an error during marshalling.
*/
public void readMarshallable(T t, @NotNull WireIn in, boolean overwrite) throws InvalidMarshallableException {
// Choose the reading method based on the hint
if (in.hintReadInputOrder())
readMarshallableInputOrder(t, in, overwrite);
else
readMarshallableDTOOrder(t, in, overwrite);
// Validate the object after reading
ValidatableUtil.validate(t);
}
/**
* Reads and populates the DTO based on the provided order.
*
* @param t Target object to populate with read values.
* @param in Input source from which values are read.
* @param overwrite Flag indicating whether to overwrite the existing value in the target object.
* @throws InvalidMarshallableException If there is an error during marshalling.
*/
public void readMarshallableDTOOrder(T t, @NotNull WireIn in, boolean overwrite) throws InvalidMarshallableException {
try {
for (@NotNull FieldAccess field : fields) {
ValueIn vin = in.read(field.key);
field.readValue(t, defaultValue, vin, overwrite);
}
ValidatableUtil.validate(t);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
/**
* Reads and populates the DTO based on the input's order.
*
* @param t Target object to populate with read values.
* @param in Input source from which values are read.
* @param overwrite Flag indicating whether to overwrite the existing value in the target object.
* @throws InvalidMarshallableException If there is an error during marshalling.
*/
public void readMarshallableInputOrder(T t, @NotNull WireIn in, boolean overwrite) throws InvalidMarshallableException {
try (ScopedResource stlSb = Wires.acquireStringBuilderScoped()) {
StringBuilder sb = stlSb.get();
// Iterating over all fields to read their values
for (int i = 0; i < fields.length; i++) {
boolean more = in.hasMore();
FieldAccess field = fields[i];
ValueIn vin = more ? in.read(sb) : null;
// Check if fields are present and in order
if (more && matchesFieldName(sb, field)) {
field.readValue(t, defaultValue, in.getValueIn(), overwrite);
} else {
// If not, copy default values
for (; i < fields.length; i++) {
FieldAccess field2 = fields[i];
field2.setDefaultValue(defaultValue, t);
}
if (vin == null || sb.length() <= 0)
return;
// Read the next set of values if there are any left
do {
FieldAccess fieldAccess = fieldMap.get(sb);
if (fieldAccess == null)
vin.skipValue();
else
fieldAccess.readValue(t, defaultValue, vin, overwrite);
vin = in.read(sb);
} while (in.hasMore());
}
}
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
/**
* Checks if the given field name (represented by a StringBuilder) matches the field name of the provided {@link FieldAccess}.
* If the StringBuilder has a length of 0, it's assumed to match any field name.
*
* @param sb The StringBuilder containing the field name to be checked.
* @param field The {@link FieldAccess} whose field name needs to be matched against.
* @return True if the field name matches or if the StringBuilder is empty; False otherwise.
*/
public boolean matchesFieldName(StringBuilder sb, FieldAccess field) {
return sb.length() == 0 || StringUtils.equalsCaseIgnore(field.field.getName(), sb);
}
/**
* Writes the key representation of the given object to the provided {@link Bytes} destination.
* As per the assumption, only the first field (key) of the object is written.
*
* @param t The object whose key needs to be written.
* @param bytes The destination {@link Bytes} where the object's key representation will be written.
*/
public void writeKey(T t, Bytes> bytes) {
// assume one key for now.
try {
fields[0].getAsBytes(t, bytes);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
/**
* Compares two objects field by field to determine their equality.
* Uses each field's {@link FieldAccess} to perform the equality check.
*
* @param o1 The first object to compare.
* @param o2 The second object to compare.
* @return True if all fields of both objects are equal; False if at least one field differs.
*/
public boolean isEqual(Object o1, Object o2) {
for (@NotNull FieldAccess field : fields) {
if (!field.isEqual(o1, o2))
return false;
}
return true;
}
/**
* Fetches the value of the specified field from the provided object.
*
* @param o The object from which the field value needs to be fetched.
* @param name The name of the field whose value is to be fetched.
* @return The value of the specified field from the object.
* @throws NoSuchFieldException If no field with the specified name is found in the object.
*/
public Object getField(Object o, String name) throws NoSuchFieldException {
try {
FieldAccess field = fieldMap.get(name);
if (field == null)
throw new NoSuchFieldException(name);
return field.field.get(o);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
/**
* Retrieves the value of a specified field from the provided object and converts it to a long.
* If the field's type is not inherently long or int, it attempts a conversion using the ObjectUtils class.
*
* @param o The object from which the field value is to be retrieved.
* @param name The name of the field whose value needs to be fetched.
* @return The long value of the specified field.
* @throws NoSuchFieldException If no field with the specified name is found in the object.
*/
public long getLongField(@NotNull Object o, String name) throws NoSuchFieldException {
try {
FieldAccess field = fieldMap.get(name);
if (field == null)
throw new NoSuchFieldException(name);
Field field2 = field.field;
return field2.getType() == long.class
? field2.getLong(o)
: field2.getType() == int.class
? field2.getInt(o)
: ObjectUtils.convertTo(Long.class, field2.get(o));
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
/**
* Sets the value of a specified field in the provided object.
* If the type of the value does not directly match the field's type, it attempts a conversion using the ObjectUtils class.
*
* @param o The object in which the field's value needs to be set.
* @param name The name of the field whose value needs to be set.
* @param value The value to set to the field.
* @throws NoSuchFieldException If no field with the specified name is found in the object.
*/
public void setField(Object o, String name, Object value) throws NoSuchFieldException {
try {
FieldAccess field = fieldMap.get(name);
if (field == null)
throw new NoSuchFieldException(name);
@NotNull final Field field2 = field.field;
value = ObjectUtils.convertTo(field2.getType(), value);
field2.set(o, value);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
/**
* Sets a long value to a specified field in the provided object.
* If the field's type is not inherently long or int, it attempts a conversion using the ObjectUtils class.
*
* @param o The object in which the field's value needs to be set.
* @param name The name of the field whose value needs to be set.
* @param value The long value to set to the field.
* @throws NoSuchFieldException If no field with the specified name is found in the object.
*/
public void setLongField(Object o, String name, long value) throws NoSuchFieldException {
try {
FieldAccess field = fieldMap.get(name);
if (field == null)
throw new NoSuchFieldException(name);
@NotNull final Field field2 = field.field;
if (field2.getType() == long.class)
field2.setLong(o, value);
else if (field2.getType() == int.class)
field2.setInt(o, (int) value);
else
field2.set(o, ObjectUtils.convertTo(field2.getType(), value));
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
/**
* Returns the default value of type T.
*
* @return The default value of type T.
*/
@Nullable
public T defaultValue() {
return defaultValue;
}
/**
* Resets the fields of the given object 'o' to the default value.
*
* @param o The object whose fields are to be reset to the default value.
*/
public void reset(T o) {
try {
for (FieldAccess field : fields)
field.setDefaultValue(defaultValue, o);
} catch (IllegalAccessException e) {
// should never happen as the types should match.
throw new AssertionError(e);
}
}
/**
* Checks if the current WireMarshaller is a leaf.
*
* @return true if the WireMarshaller is a leaf, false otherwise.
*/
public boolean isLeaf() {
return isLeaf;
}
/**
* Provides a field accessor that's specialized for handling fields which require
* conversion between integer values and string representations using a LongConverter.
*/
static class LongConverterFieldAccess extends FieldAccess {
// The LongConverter instance used for conversion operations.
@NotNull
private final LongConverter longConverter;
/**
* Constructor to initialize field access with a specific LongConverter.
*
* @param field The field being accessed.
* @param longConverter The converter to use for this field.
*/
LongConverterFieldAccess(@NotNull Field field, @NotNull LongConverter longConverter) {
super(field);
this.longConverter = longConverter;
}
/**
* Fetches the LongConverter instance associated with a given class.
* Tries to retrieve a static "INSTANCE" field or creates a new instance if not found.
*
* @param clazz The class which presumably has a LongConverter.
* @return The LongConverter instance.
*/
static LongConverter getInstance(Class> clazz) {
try {
Field converterField = clazz.getDeclaredField("INSTANCE");
return (LongConverter) converterField.get(null);
} catch (NoSuchFieldException nsfe) {
return (LongConverter) ObjectUtils.newInstance(clazz);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/**
* Reads the long value from an object and writes it using the provided ValueOut writer.
*
* @param o The source object.
* @param write The writer for output.
* @param previous The previous value (currently not used).
*/
@Override
protected void getValue(Object o, @NotNull ValueOut write, @Nullable Object previous) {
long aLong = getLong(o);
if (write.isBinary()) {
write.int64(aLong);
} else {
try (ScopedResource stlSb = Wires.acquireStringBuilderScoped()) {
StringBuilder sb = stlSb.get();
longConverter.append(sb, aLong);
if (!write.isBinary() && sb.length() == 0)
write.text("");
else if (longConverter.allSafeChars() || noUnsafeChars(sb))
write.rawText(sb);
else
write.text(sb);
}
}
}
private boolean noUnsafeChars(StringBuilder sb) {
int index = sb.length() - 1;
if (sb.charAt(0) == ' ' || sb.charAt(index) == ' ')
return false;
for (int i = 0; i < sb.length(); i++) {
if (":'\"#,".indexOf(sb.charAt(i)) >= 0)
return false;
}
return true;
}
/**
* Retrieves the long value from an object.
*
* @param o The object from which to retrieve the value.
* @return The long value of the field.
*/
protected long getLong(Object o) {
return unsafeGetLong(o, offset);
}
/**
* Sets the value of the object's field based on the provided ValueIn reader.
*
* @param o The target object.
* @param read The reader for input.
* @param overwrite Whether to overwrite existing values (currently not used).
*/
@Override
protected void setValue(Object o, @NotNull ValueIn read, boolean overwrite) {
long i;
if (read.isBinary()) {
i = read.int64();
} else {
try (ScopedResource stlSb = Wires.acquireStringBuilderScoped()) {
StringBuilder sb = stlSb.get();
read.text(sb);
i = longConverter.parse(sb);
if (!rangeCheck(i))
throw new IORuntimeException("value '" + sb + "' is out of range for a " + field.getType());
}
}
setLong(o, i);
}
/**
* Checks if the provided long value is within acceptable ranges.
*
* @param i The long value to check.
* @return True if the value is within range; otherwise, false.
*/
protected boolean rangeCheck(long i) {
return true;
}
/**
* Sets a long value to the field of an object.
*
* @param o The target object.
* @param i The value to set.
*/
protected void setLong(Object o, long i) {
unsafePutLong(o, offset, i);
}
/**
* Reads a string value from the object and writes its long representation to the provided bytes.
*
* @param o The source object.
* @param bytes The bytes to write the long representation to.
*/
@Override
public void getAsBytes(Object o, @NotNull Bytes> bytes) {
try (ScopedResource stlSb = Wires.acquireStringBuilderScoped()) {
StringBuilder sb = stlSb.get();
bytes.readUtf8(sb);
long i = longConverter.parse(sb);
bytes.writeLong(i);
}
}
/**
* Checks if two objects have the same long value for the accessed field.
*
* @param o First object.
* @param o2 Second object.
* @return True if values are the same; otherwise, false.
*/
@Override
protected boolean sameValue(Object o, Object o2) {
return getLong(o) == getLong(o2);
}
/**
* Copies the long value of the accessed field from one object to another.
*
* @param from Source object.
* @param to Destination object.
*/
@Override
protected void copy(Object from, Object to) {
setLong(to, getLong(from));
}
}
/**
* Abstract class to manage access to fields of objects.
* This class provides utility methods to read and write fields from/to objects.
*/
abstract static class FieldAccess {
@NotNull
final Field field;
final long offset;
@NotNull
final WireKey key;
Comment commentAnnotation;
Boolean isLeaf;
/**
* Constructor initializing field with given value.
*
* @param field Field to be accessed.
*/
FieldAccess(@NotNull Field field) {
this(field, null);
}
/**
* Constructor initializing field and isLeaf with given values.
*
* @param field Field to be accessed.
* @param isLeaf Flag to indicate whether the field is a leaf node.
*/
FieldAccess(@NotNull Field field, Boolean isLeaf) {
this.field = field;
offset = unsafeObjectFieldOffset(field);
key = field::getName;
this.isLeaf = isLeaf;
try {
commentAnnotation = Jvm.findAnnotation(field, Comment.class);
} catch (NullPointerException ignore) {
}
}
// ... (code continues)
/**
* Create a specific FieldAccess object based on the field type.
*
* @param field Field for which FieldAccess object is created.
* @return FieldAccess object specific to the field type.
*/
@Nullable
public static Object create(@NotNull Field field, @Nullable Object defaultObject) {
Class> type = field.getType();
try {
if (type.isArray()) {
if (type.getComponentType() == byte.class)
return new ByteArrayFieldAccess(field);
return new ArrayFieldAccess(field);
}
if (EnumSet.class.isAssignableFrom(type)) {
final Class> componentType = extractClass(computeActualTypeArguments(EnumSet.class, field)[0]);
if (componentType == Object.class || Modifier.isAbstract(componentType.getModifiers()))
throw new RuntimeException("Could not get enum constant directory");
boolean isLeaf = !Throwable.class.isAssignableFrom(componentType)
&& WIRE_MARSHALLER_CL.get(componentType).isLeaf;
try {
Object[] values = (Object[]) Jvm.getMethod(componentType, "values").invoke(componentType);
return new EnumSetFieldAccess(field, isLeaf, values, componentType);
} catch (IllegalAccessException | InvocationTargetException e) {
throw Jvm.rethrow(e);
}
}
if (Collection.class.isAssignableFrom(type))
return CollectionFieldAccess.of(field);
if (Map.class.isAssignableFrom(type))
return new MapFieldAccess(field);
switch (type.getName()) {
case "boolean":
return new BooleanFieldAccess(field);
case "byte": {
LongConverter longConverter = acquireLongConverter(field);
if (longConverter != null)
return new ByteLongConverterFieldAccess(field, longConverter);
return new ByteFieldAccess(field);
}
case "char": {
LongConverter longConverter = acquireLongConverter(field);
if (longConverter != null)
return new CharLongConverterFieldAccess(field, longConverter);
return new CharFieldAccess(field);
}
case "short": {
LongConverter longConverter = acquireLongConverter(field);
if (longConverter != null)
return new ShortLongConverterFieldAccess(field, longConverter);
return new ShortFieldAccess(field);
}
case "int": {
LongConverter longConverter = acquireLongConverter(field);
if (longConverter != null)
return new IntLongConverterFieldAccess(field, longConverter);
return new IntegerFieldAccess(field);
}
case "float":
return new FloatFieldAccess(field);
case "long": {
LongConverter longConverter = acquireLongConverter(field);
return longConverter == null
? new LongFieldAccess(field)
: new LongConverterFieldAccess(field, longConverter);
}
case "double":
return new DoubleFieldAccess(field);
case "java.lang.String":
return new StringFieldAccess(field);
case "java.lang.StringBuilder":
return new StringBuilderFieldAccess(field, defaultObject);
case "net.openhft.chronicle.bytes.Bytes":
return new BytesFieldAccess(field);
default:
if (isRecord != null && (boolean) isRecord.invoke(type))
throw new UnsupportedOperationException("Record classes are not supported");
@Nullable Boolean isLeaf = null;
if (IntValue.class.isAssignableFrom(type))
return new IntValueAccess(field);
if (LongValue.class.isAssignableFrom(type))
return new LongValueAccess(field);
if (WireMarshaller.class.isAssignableFrom(type))
isLeaf = WIRE_MARSHALLER_CL.get(type).isLeaf;
else if (isCollection(type))
isLeaf = false;
Object defaultValue = defaultObject == null ? null : field.get(defaultObject);
if (defaultValue != null && defaultValue instanceof Resettable && !(defaultValue instanceof DynamicEnum))
return new ResettableFieldAccess(field, isLeaf, defaultValue);
return new ObjectFieldAccess(field, isLeaf);
}
} catch (IllegalAccessException | InvocationTargetException ex) {
throw Jvm.rethrow(ex);
}
}
/**
* Acquires a LongConverter instance associated with a given field, if available.
*
* This method checks if the provided field has a LongConversion annotation. If present,
* it retrieves the corresponding LongConverter using the LongConverterFieldAccess helper class.
*
* @param field The field for which the LongConverter needs to be obtained
* @return The associated LongConverter instance, or null if not present
*/
@Nullable
private static LongConverter acquireLongConverter(@NotNull Field field) {
LongConversion longConversion = Jvm.findAnnotation(field, LongConversion.class);
LongConverter longConverter = null;
if (longConversion != null)
longConverter = LongConverterFieldAccess.getInstance(longConversion.value());
return longConverter;
}
/**
* Extracts the raw Class type from a given Type object.
*
* This method aims to handle various Type representations, like Class or ParameterizedType,
* and return the underlying Class representation.
*
* @param type0 The type from which the class should be extracted
* @return The extracted Class representation
*/
@NotNull
static Class> extractClass(Type type0) {
if (type0 instanceof Class)
return (Class) type0;
else if (type0 instanceof ParameterizedType)
return (Class) ((ParameterizedType) type0).getRawType();
else
return Object.class;
}
@NotNull
@Override
public String toString() {
return "FieldAccess{" +
"field=" + field +
", isLeaf=" + isLeaf +
'}';
}
/**
* Writes the value of a given object's field to a provided WireOut instance.
*
* This method serializes the value of the specified object's field and writes it to
* the WireOut. If the field has a Comment annotation, a special processing is done
* using the CommentAnnotationNotifier.
*
* @param o The object containing the field to be written
* @param out The WireOut instance to write the field value to
* @throws IllegalAccessException If the field cannot be accessed
* @throws InvalidMarshallableException If the object cannot be marshalled
*/
void write(Object o, @NotNull WireOut out) throws IllegalAccessException, InvalidMarshallableException {
ValueOut valueOut = out.write(field.getName());
if (valueOut instanceof CommentAnnotationNotifier && this.commentAnnotation != null) {
getValueCommentAnnotated(o, out, valueOut);
return;
}
getValue(o, valueOut, null);
}
/**
* Retrieves the value of the provided object's field and writes it to the provided WireOut instance,
* appending a comment based on the associated Comment annotation.
*
* If the field has a Comment annotation, its value is formatted using the field's value and appended as a comment.
* The CommentAnnotationNotifier is used to indicate that the written value is preceded by a comment.
*
* @param o The object containing the field whose value needs to be retrieved
* @param out The WireOut instance to which the value and the comment are written
* @param valueOut The ValueOut instance representing the field's value
* @throws IllegalAccessException If the field cannot be accessed
* @throws InvalidMarshallableException If the object cannot be marshalled
*/
private void getValueCommentAnnotated(Object o, @NotNull WireOut out, ValueOut valueOut) throws IllegalAccessException, InvalidMarshallableException {
CommentAnnotationNotifier notifier = (CommentAnnotationNotifier) valueOut;
notifier.hasPrecedingComment(true);
try {
getValue(o, valueOut, null);
out.writeComment(String.format(this.commentAnnotation.value(), field.get(o)));
} finally {
notifier.hasPrecedingComment(false);
}
}
/**
* Writes the value of the field from the provided object to the output. If the value is the same
* as the previous value, it skips the writing. If the copy flag is set, it also copies the value
* from the source object to the previous object.
*
* @param o Object from which the field value is fetched.
* @param out Output destination where the value is written to.
* @param previous Previous object to compare for sameness and optionally copy to.
* @param copy Flag indicating whether to copy the value from source to the previous object.
* @throws IllegalAccessException If there's an access violation when fetching the field value.
* @throws InvalidMarshallableException If there's an error during marshalling.
*/
void write(Object o, @NotNull WireOut out, Object previous, boolean copy) throws IllegalAccessException, InvalidMarshallableException {
// Check if the current and previous values are the same
if (sameValue(o, previous))
return;
// Write the field's value to the output
ValueOut write = out.write(field.getName());
getValue(o, write, previous);
// Copy value from source object to previous object, if required
if (copy)
copy(o, previous);
}
/**
* Check if the values of a field in two objects are the same.
*
* @param o First object.
* @param o2 Second object.
* @return true if values are the same, false otherwise.
* @throws IllegalAccessException If unable to access the field.
*/
protected boolean sameValue(Object o, Object o2) throws IllegalAccessException {
final Object v1 = field.get(o);
final Object v2 = field.get(o2);
if (v1 instanceof CharSequence && v2 instanceof CharSequence)
return StringUtils.isEqual((CharSequence) v1, (CharSequence) v2);
return Objects.equals(v1, v2);
}
/**
* Copies the value of a field from one object to another.
*
* @param from Source object.
* @param to Destination object.
* @throws IllegalAccessException If unable to access the field.
*/
protected void copy(Object from, Object to) throws IllegalAccessException {
ObjectUtils.requireNonNull(from);
ObjectUtils.requireNonNull(to);
unsafePutObject(to, offset, unsafeGetObject(from, offset));
}
/**
* Abstract method to get the value of a field from an object.
*
* @param o Object from which to get the value.
* @param write Output destination.
* @param previous Previous object for comparison.
* @throws IllegalAccessException If unable to access the field.
* @throws InvalidMarshallableException If marshalling fails.
*/
protected abstract void getValue(Object o, ValueOut write, Object previous) throws IllegalAccessException, InvalidMarshallableException;
/**
* Reads the value of a field from an input and sets it in an object.
*
* @param o Object to set the value in.
* @param defaults Default values.
* @param read Input source.
* @param overwrite Whether to overwrite existing value.
* @throws IllegalAccessException If unable to access the field.
* @throws InvalidMarshallableException If marshalling fails.
*/
protected void readValue(Object o, Object defaults, ValueIn read, boolean overwrite) throws IllegalAccessException, InvalidMarshallableException {
if (!read.isPresent()) {
if (overwrite && defaults != null)
copy(Objects.requireNonNull(defaults), o);
} else {
long pos = read.wireIn().bytes().readPosition();
try {
setValue(o, read, overwrite);
} catch (UnexpectedFieldHandlingException | ClassCastException | ClassNotFoundRuntimeException e) {
Jvm.rethrow(e);
} catch (Exception e) {
read.wireIn().bytes().readPosition(pos);
try (ScopedResource stlSb = Wires.acquireStringBuilderScoped()) {
StringBuilder sb = stlSb.get();
read.text(sb);
Jvm.warn().on(getClass(), "Failed to read '" + this.field.getName() + "' with '" + sb + "' taking default", e);
}
copy(defaults, o);
}
}
}
/**
* Abstract method to set the value of a field in an object.
*
* @param o Object to set the value in.
* @param read Input source.
* @param overwrite Whether to overwrite existing value.
* @throws IllegalAccessException If unable to access the field.
*/
protected abstract void setValue(Object o, ValueIn read, boolean overwrite) throws IllegalAccessException;
/**
* Abstract method to reset the value of a field in an object to default value.
* The default value is the one present in objects of that class after no-argument constructor.
* Where possible, existing data structures should be preserved without reallocation to avoid garbage.
*
* @param defaultObject A reference unmodified instance of this class.
* @param o Object to reset the value in.
*/
protected void setDefaultValue(Object defaultObject, Object o) throws IllegalAccessException {
copy(defaultObject, o);
}
/**
* Abstract method to convert the value of a field in an object to bytes.
*
* @param o Object containing the field.
* @param bytes Destination to write the bytes to.
* @throws IllegalAccessException If unable to access the field.
*/
public abstract void getAsBytes(Object o, Bytes> bytes) throws IllegalAccessException;
/**
* Checks whether the values of a field in two objects are equal.
*
* @param o1 First object.
* @param o2 Second object.
* @return true if the values are equal, false otherwise.
*/
public boolean isEqual(Object o1, Object o2) {
try {
return sameValue(o1, o2);
} catch (IllegalAccessException e) {
return false;
}
}
}
/**
* This is a specialized FieldAccess implementation for fields of type IntValue.
* It provides implementations for reading and writing the IntValue from and to various sources.
*/
static class IntValueAccess extends FieldAccess {
/**
* Constructor for the IntValueAccess class.
*
* @param field The field this FieldAccess is responsible for.
*/
IntValueAccess(@NotNull Field field) {
super(field);
}
@Override
protected void getValue(Object o, ValueOut write, Object previous) throws IllegalAccessException {
IntValue f = (IntValue) field.get(o);
int value = f == null ? 0 : f.getValue();
write.int32forBinding(value);
}
@Override
protected void setValue(Object o, ValueIn read, boolean overwrite) throws IllegalAccessException {
IntValue f = (IntValue) field.get(o);
if (f == null) {
f = read.wireIn().newIntReference();
field.set(o, f);
}
read.int32(f);
}
@Override
public void getAsBytes(Object o, Bytes> bytes) {
throw new UnsupportedOperationException();
}
}
/**
* This is a specialized FieldAccess implementation for fields of type LongValue.
* It provides implementations for reading and writing the LongValue from and to various sources.
*/
static class LongValueAccess extends FieldAccess {
/**
* Constructor for the LongValueAccess class.
*
* @param field The field this FieldAccess is responsible for.
*/
LongValueAccess(@NotNull Field field) {
super(field);
}
@Override
protected void getValue(Object o, ValueOut write, Object previous) throws IllegalAccessException {
LongValue f = (LongValue) field.get(o);
long value = f == null ? 0 : f.getValue();
write.int64forBinding(value);
}
@Override
protected void setValue(Object o, ValueIn read, boolean overwrite) throws IllegalAccessException {
LongValue f = (LongValue) field.get(o);
if (f == null) {
f = read.wireIn().newLongReference();
field.set(o, f);
}
read.int64(f);
}
@Override
public void getAsBytes(Object o, Bytes> bytes) {
throw new UnsupportedOperationException();
}
}
/**
* This is a specialized FieldAccess implementation for generic object fields.
* It provides methods for reading and writing object fields from and to various sources,
* taking into account special cases where the field may be marshaled differently based on annotations.
*/
static class ObjectFieldAccess extends FieldAccess {
private final Class> type; // Type of the object field
private final AsMarshallable asMarshallable; // Annotation indicating if the field should be treated as marshallable
/**
* Constructor for the ObjectFieldAccess class.
*
* @param field The field this FieldAccess is responsible for.
* @param isLeaf A flag indicating whether the field is a leaf node.
*/
ObjectFieldAccess(@NotNull Field field, Boolean isLeaf) {
super(field, isLeaf);
// Get annotations and field type
asMarshallable = Jvm.findAnnotation(field, AsMarshallable.class);
type = field.getType();
}
@Override
protected void getValue(@NotNull Object o, @NotNull ValueOut write, Object previous)
throws IllegalAccessException, InvalidMarshallableException {
Boolean wasLeaf = null;
if (isLeaf != null)
wasLeaf = write.swapLeaf(isLeaf);
assert o != null;
Object v = field.get(o);
if (asMarshallable == null || !(v instanceof WriteMarshallable))
write.object(type, v);
else
write.typedMarshallable((WriteMarshallable) v);
if (wasLeaf != null)
write.swapLeaf(wasLeaf);
}
@Override
protected void setValue(Object o, @NotNull ValueIn read, boolean overwrite) throws IllegalAccessException {
long pos = read.wireIn().bytes().readPosition();
try {
@Nullable Object using = ObjectUtils.isImmutable(type) == ObjectUtils.Immutability.NO ? field.get(o) : null;
Object object = null;
try {
object = read.object(using, type, false);
} catch (Exception e) {
// "Unhandled" Abstract classes that are not types should be null (Enums are abstract classes in Java but should not be null here)
if (using == null &&
Modifier.isAbstract(type.getModifiers()) &&
!Modifier.isInterface(type.getModifiers()) &&
!type.isEnum() &&
!read.isTyped()) {
// retain the null value of object
Jvm.warn().on(getClass(), "Ignoring exception and setting field '" + field.getName() + "' to null", e);
} else {
Jvm.rethrow(e);
}
}
if (object instanceof SingleThreadedChecked)
((SingleThreadedChecked) object).singleThreadedCheckReset();
field.set(o, object);
} catch (UnexpectedFieldHandlingException | ClassCastException | ClassNotFoundRuntimeException e) {
Jvm.rethrow(e);
} catch (Exception e) {
read.wireIn().bytes().readPosition(pos);
Jvm.warn().on(getClass(), "Unable to parse field: " + field.getName() + ", as a marshallable as it is " + read.objectBestEffort(), e);
if (overwrite)
field.set(o, ObjectUtils.defaultValue(field.getType()));
}
}
@Override
public void getAsBytes(Object o, @NotNull Bytes> bytes) throws IllegalAccessException {
bytes.writeUtf8(String.valueOf(field.get(o)));
}
}
static class ResettableFieldAccess extends ObjectFieldAccess {
private final Object defaultValue;
ResettableFieldAccess(@NotNull Field field, Boolean isLeaf, Object defaultValue) {
super(field, isLeaf);
this.defaultValue = defaultValue;
}
@Override
protected void setDefaultValue(Object defaultObject, Object o) throws IllegalAccessException {
Object existingValue = unsafeGetObject(o, offset);
if (existingValue == defaultValue)
return;
if (existingValue != null && existingValue.getClass() == defaultValue.getClass()) {
((Resettable) existingValue).reset();
return;
}
super.setDefaultValue(defaultObject, o);
}
}
static class StringFieldAccess extends FieldAccess {
StringFieldAccess(@NotNull Field field) {
super(field, false); // Strings are not leaf nodes, hence 'false'
}
@Override
protected void getValue(Object o, @NotNull ValueOut write, Object previous) {
write.text(UnsafeMemory.unsafeGetObject(o, offset));
}
@Override
protected void setValue(Object o, @NotNull ValueIn read, boolean overwrite) {
unsafePutObject(o, offset, read.text());
}
@Override
public void getAsBytes(Object o, @NotNull Bytes> bytes) {
bytes.writeUtf8(unsafeGetObject(o, offset));
}
}
/**
* This is a specialized FieldAccess implementation for StringBuilder fields.
* It provides methods to efficiently read and write StringBuilder fields from
* and to various sources, using unsafe operations for performance optimization.
*/
static class StringBuilderFieldAccess extends FieldAccess {
private StringBuilder defaultValue;
public StringBuilderFieldAccess(@NotNull Field field, @Nullable Object defaultObject) throws IllegalAccessException {
super(field, true);
this.defaultValue = defaultObject == null ? null : (StringBuilder) field.get(defaultObject);
}
@Override
protected void getValue(Object o, @NotNull ValueOut write, Object previous) {
@NotNull CharSequence cs = unsafeGetObject(o, offset);
write.text(cs);
}
@Override
protected void setValue(Object o, @NotNull ValueIn read, boolean overwrite) {
StringBuilder sb = unsafeGetObject(o, offset);
if (sb == null) {
sb = new StringBuilder();
unsafePutObject(o, offset, sb);
}
if (read.textTo(sb) == null)
unsafePutObject(o, offset, null);
}
@Override
protected void setDefaultValue(Object defaultObject, Object o) throws IllegalAccessException {
if (defaultValue == null) {
super.setDefaultValue(defaultObject, o);
return;
}
StringBuilder sb = unsafeGetObject(o, offset);
if (sb == defaultValue)
return;
if (sb == null) {
sb = new StringBuilder();
unsafePutObject(o, offset, sb);
}
sb.setLength(0);
sb.append(defaultValue);
}
@Override
public void getAsBytes(Object o, @NotNull Bytes> bytes) {
bytes.writeUtf8((CharSequence) unsafeGetObject(o, offset));
}
@Override
protected boolean sameValue(Object o1, Object o2) throws IllegalAccessException {
return StringUtils.isEqual((StringBuilder) field.get(o1), (StringBuilder) field.get(o2));
}
@Override
protected void copy(Object from, Object to) {
final StringBuilder fromSequence = unsafeGetObject(from, offset);
StringBuilder toSequence = unsafeGetObject(to, offset);
if (fromSequence == null) {
unsafePutObject(to, offset, null);
return;
} else if (toSequence == null) {
toSequence = new StringBuilder();
unsafePutObject(to, offset, toSequence);
}
toSequence.setLength(0);
toSequence.append(fromSequence);
}
}
/**
* This is a specialized FieldAccess implementation for Bytes fields.
* It provides methods to efficiently read and write Bytes fields from
* and to various sources, using unsafe operations for performance optimization.
*/
static class BytesFieldAccess extends FieldAccess {
/**
* Constructor for the BytesFieldAccess class.
*
* @param field The Bytes field this FieldAccess is responsible for.
*/
BytesFieldAccess(@NotNull Field field) {
super(field, false); // Bytes is not treated as a leaf node
}
@Override
protected void getValue(@NotNull Object o, @NotNull ValueOut write, Object previous)
throws IllegalAccessException {
Bytes> bytesField = (Bytes) field.get(o);
write.bytes(bytesField);
}
@Override
protected void setValue(Object o, @NotNull ValueIn read, boolean overwrite) {
@NotNull Bytes> bytes = unsafeGetObject(o, offset);
if (bytes == null)
unsafePutObject(o, offset, bytes = Bytes.allocateElasticOnHeap(128));
WireIn wireIn = read.wireIn();
if (wireIn instanceof TextWire) {
wireIn.consumePadding();
if (wireIn.bytes().startsWith(TextWire.BINARY)) {
decodeBytes(read, bytes);
return;
}
}
if (read.textTo(bytes) == null)
unsafePutObject(o, offset, null);
else
bytes.singleThreadedCheckReset();
}
/**
* Helper method to decode a Base64-encoded text value into a Bytes instance.
*
* @param read The ValueIn instance containing the Base64-encoded text value.
* @param bytes The Bytes instance where the decoded value will be stored.
*/
private void decodeBytes(@NotNull ValueIn read, Bytes> bytes) {
try (ScopedResource stlSb = Wires.acquireStringBuilderScoped()) {
@NotNull StringBuilder sb0 = stlSb.get();
read.text(sb0);
String s = WireInternal.INTERNER.intern(sb0);
byte[] decode = Base64.getDecoder().decode(s);
bytes.clear();
bytes.write(decode);
}
}
@Override
public void getAsBytes(Object o, @NotNull Bytes> bytes) throws IllegalAccessException {
Bytes> bytesField = (Bytes) field.get(o);
bytes.write(bytesField);
}
@Override
protected void copy(Object from, Object to) {
Bytes> fromBytes = unsafeGetObject(from, offset);
Bytes> toBytes = unsafeGetObject(to, offset);
if (fromBytes == toBytes)
return;
if (fromBytes == null) {
unsafePutObject(to, offset, null);
return;
} else if (toBytes == null) {
toBytes = Bytes.elasticByteBuffer();
unsafePutObject(to, offset, toBytes);
}
toBytes.clear();
toBytes.write(fromBytes);
}
}
/**
* The ArrayFieldAccess class extends FieldAccess to provide specialized access
* and manipulation methods for fields that are arrays.
*
* It calculates the component type of the array and retrieves its equivalent object type
* for ease of use. The class is designed to handle arrays in a generic manner and
* use the provided methods of the superclass for actual field manipulation.
*/
static class ArrayFieldAccess extends FieldAccess {
private final Class> componentType;
private final Class> objectType;
ArrayFieldAccess(@NotNull Field field) {
super(field);
componentType = field.getType().getComponentType();
objectType = ObjectUtils.implementationToUse(
ObjectUtils.primToWrapper(componentType));
}
@Override
protected void getValue(Object o, @NotNull ValueOut write, Object previous) throws IllegalAccessException {
Object arr = field.get(o);
boolean leaf = write.swapLeaf(true);
if (arr == null)
write.nu11();
else
write.sequence(arr, (array, out) -> {
for (int i = 0, len = Array.getLength(array); i < len; i++)
out.object(objectType, Array.get(array, i));
});
write.swapLeaf(leaf);
}
@Override
protected void setValue(Object o, @NotNull ValueIn read, boolean overwrite) throws IllegalAccessException {
final Object arr = field.get(o);
if (read.isNull()) {
if (arr != null)
field.set(o, null);
return;
}
@NotNull List list = new ArrayList();
read.sequence(list, (l, in) -> {
while (in.hasNextSequenceItem())
l.add(in.object(componentType));
});
Object arr2 = Array.newInstance(componentType, list.size());
for (int i = 0; i < list.size(); i++)
Array.set(arr2, i, list.get(i));
field.set(o, arr2);
}
@Override
public void getAsBytes(Object o, Bytes> bytes) {
throw new UnsupportedOperationException();
}
@Override
public boolean isEqual(Object o1, Object o2) {
try {
Object a1 = field.get(o1);
Object a2 = field.get(o2);
if (a1 == null) return a2 == null;
if (a2 == null) return false;
Class> aClass1 = a1.getClass();
Class> aClass2 = a2.getClass();
if (aClass1 != aClass2)
if (!aClass1.isAssignableFrom(aClass2) && !aClass2.isAssignableFrom(aClass1))
return false;
int len1 = Array.getLength(a1);
int len2 = Array.getLength(a2);
if (len1 != len2)
return false;
for (int i = 0; i < len1; i++)
if (!Objects.equals(Array.get(a1, i), Array.get(a2, i)))
return false;
return true;
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
}
/**
* The ByteArrayFieldAccess class extends FieldAccess to provide specialized access
* and manipulation methods for fields that are byte arrays.
*
* The class is optimized for reading and writing byte arrays to and from a wire format
* while preserving encapsulation and supporting null values.
*/
static class ByteArrayFieldAccess extends FieldAccess {
ByteArrayFieldAccess(@NotNull Field field) {
super(field);
}
@Override
protected void getValue(Object o, @NotNull ValueOut write, Object previous) throws IllegalAccessException {
Object arr = field.get(o);
boolean leaf = write.swapLeaf(true);
if (arr == null)
write.nu11();
else
write.bytes((byte[]) arr);
write.swapLeaf(leaf);
}
@Override
protected void setValue(Object o, @NotNull ValueIn read, boolean overwrite) throws IllegalAccessException {
final Object arr = field.get(o);
if (read.isNull()) {
if (arr != null)
field.set(o, null);
return;
}
byte[] arr2 = read.bytes((byte[]) arr);
if (arr2 != arr)
field.set(o, arr2);
}
@Override
public void getAsBytes(Object o, Bytes> bytes) {
throw new UnsupportedOperationException();
}
@Override
public boolean isEqual(Object o1, Object o2) {
try {
Object a1 = field.get(o1);
Object a2 = field.get(o2);
if (a1 == null) return a2 == null;
if (a2 == null) return false;
Class> aClass1 = a1.getClass();
Class> aClass2 = a2.getClass();
if (aClass1 != aClass2)
if (!aClass1.isAssignableFrom(aClass2) && !aClass2.isAssignableFrom(aClass1))
return false;
return Arrays.equals((byte[]) a1, (byte[]) a2);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
}
/**
* The EnumSetFieldAccess class extends FieldAccess to provide specialized access and manipulation methods
* for fields that are of type {@link EnumSet}.
*
* This class allows efficient reading and writing of EnumSet fields to and from a wire format while
* preserving encapsulation and supporting null values.
*/
static class EnumSetFieldAccess extends FieldAccess {
// An array of enum values
private final Object[] values;
// The sequence getter function used for iterating and retrieving enum values from EnumSet
private final BiConsumer