net.openhft.chronicle.bytes.BytesMarshaller Maven / Gradle / Ivy
/*
* Copyright (c) 2016-2022 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.bytes;
import net.openhft.chronicle.bytes.internal.BytesInternal;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.Maths;
import net.openhft.chronicle.core.io.*;
import net.openhft.chronicle.core.util.ClassLocal;
import net.openhft.chronicle.core.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.*;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.util.*;
import java.util.function.Supplier;
import static net.openhft.chronicle.core.Jvm.uncheckedCast;
/**
* This class is used to marshal objects into bytes and unmarshal them from bytes.
* The object's fields are read and written in a streaming manner, with the ability
* to handle different types of fields. It uses a map to track the fields of the class
* and their corresponding values. This class makes use of the {@link FieldAccess} for
* actual field value extraction and setting.
*
* This class also provides a method for extracting all fields from a class and its
* superclasses (except transient and static fields). All the fields are made accessible
* even if they are private, and are then stored in a map for future access.
*
*
Note: This class suppresses rawtypes and unchecked warnings.
*
* @param the type of the object to be marshaled.
*/
@SuppressWarnings("rawtypes")
public class BytesMarshaller {
/**
* Provides a ClassLocal instance for holding a unique BytesMarshaller for each class.
*/
public static final ClassLocal BYTES_MARSHALLER_CL
= ClassLocal.withInitial(BytesMarshaller::new);
private final FieldAccess[] fields;
/**
* Constructs a BytesMarshaller for the specified class.
*
* @param tClass the class for which the BytesMarshaller is to be created.
*/
public BytesMarshaller(@NotNull Class tClass) {
final Map map = new LinkedHashMap<>();
getAllField(tClass, map);
fields = map.values().stream()
.map(FieldAccess::create)
.toArray(FieldAccess[]::new);
}
/**
* Extracts all fields from the specified class and its superclasses and stores
* them in the provided map. Only non-static and non-transient fields are considered.
*
* @param clazz the class from which to extract fields.
* @param map the map in which to store the fields.
*/
public static void getAllField(@NotNull Class> clazz, @NotNull Map map) {
if (clazz != Object.class)
getAllField(clazz.getSuperclass(), map);
for (@NotNull Field field : clazz.getDeclaredFields()) {
if ((field.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0)
continue;
Jvm.setAccessible(field);
map.put(field.getName(), field);
}
}
/**
* Reads the state of the given ReadBytesMarshallable object from the specified BytesIn.
*
* @param t the object to read into.
* @param in the BytesIn from which to read.
* @throws InvalidMarshallableException If the object cannot be read due to invalid data.
*/
public void readMarshallable(ReadBytesMarshallable t, BytesIn> in) throws InvalidMarshallableException {
for (@NotNull FieldAccess field : fields) {
field.read(t, in);
}
}
/**
* Writes the state of the given WriteBytesMarshallable object to the specified BytesOut.
*
* @param t the object to write.
* @param out the BytesOut to which to write.
* @throws IllegalArgumentException If a method is invoked with an illegal or inappropriate argument.
* @throws ClosedIllegalStateException If the resource has been released or closed.
* @throws ThreadingIllegalStateException If this resource was accessed by multiple threads in an unsafe way
* @throws BufferOverflowException If there is not enough space in the buffer.
* @throws BufferUnderflowException If there is not enough data available in the buffer.
* @throws ArithmeticException If there is an arithmetic error.
* @throws InvalidMarshallableException If the object cannot be written due to invalid data.
*/
public void writeMarshallable(WriteBytesMarshallable t, BytesOut> out)
throws IllegalArgumentException, ClosedIllegalStateException, BufferOverflowException, BufferUnderflowException, ArithmeticException, InvalidMarshallableException, ThreadingIllegalStateException {
out.adjustHexDumpIndentation(+1);
try {
for (@NotNull FieldAccess field : fields) {
field.write(t, out);
}
} catch (IllegalAccessException e) {
throw new AssertionError(e);
} finally {
out.adjustHexDumpIndentation(-1);
}
}
abstract static class FieldAccess {
final Field field;
FieldAccess(@NotNull Field field) {
this.field = field;
}
@NotNull
public static Object create(@NotNull final Field field) {
final Class> type = field.getType();
switch (type.getName()) {
case "boolean":
return new BooleanFieldAccess(field);
case "byte":
return new ByteFieldAccess(field);
case "char":
return new CharFieldAccess(field);
case "short":
return new ShortFieldAccess(field);
case "int":
return new IntegerFieldAccess(field);
case "float":
return new FloatFieldAccess(field);
case "long":
return new LongFieldAccess(field);
case "double":
return new DoubleFieldAccess(field);
default:
return nonPrimitiveFieldAccess(field, type);
}
}
static Object nonPrimitiveFieldAccess(@NotNull final Field field,
@NotNull final Class> type) {
if (type.isArray()) {
if (type.getComponentType().isPrimitive()) {
if (type == byte[].class)
return new ByteArrayFieldAccess(field);
if (type == int[].class)
return new IntArrayFieldAccess(field);
if (type == float[].class)
return new FloatArrayFieldAccess(field);
if (type == long[].class)
return new LongArrayFieldAccess(field);
if (type == double[].class)
return new DoubleArrayFieldAccess(field);
throw new UnsupportedOperationException("TODO " + field.getType());
}
return new ObjectArrayFieldAccess(field);
}
if (Collection.class.isAssignableFrom(type))
return new CollectionFieldAccess(field);
if (Map.class.isAssignableFrom(type))
return new MapFieldAccess(field);
if (BytesStore.class.isAssignableFrom(type))
return new BytesFieldAccess(field);
if (BytesMarshallable.class.isAssignableFrom(type))
return new BytesMarshallableFieldAccess(field);
return new ScalarFieldAccess(field);
}
@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 getClass().getSimpleName() + "{" +
"field=" + field +
'}';
}
void write(Object o, BytesOut> write)
throws IllegalAccessException, IllegalArgumentException, ClosedIllegalStateException, BufferOverflowException, ArithmeticException, BufferUnderflowException, InvalidMarshallableException, ThreadingIllegalStateException {
write.writeHexDumpDescription(field.getName());
getValue(o, write);
}
protected abstract void getValue(Object o, BytesOut> write)
throws IllegalAccessException, BufferOverflowException, IllegalArgumentException, ClosedIllegalStateException, BufferUnderflowException, ArithmeticException, InvalidMarshallableException, ThreadingIllegalStateException;
void read(Object o, BytesIn> read)
throws IORuntimeException, InvalidMarshallableException {
try {
setValue(o, read);
} catch (BufferUnderflowException | IllegalArgumentException | ArithmeticException |
ClosedIllegalStateException |
BufferOverflowException | IllegalAccessException iae) {
throw new IORuntimeException(iae);
}
}
protected abstract void setValue(Object o, BytesIn> read)
throws IllegalAccessException, BufferUnderflowException, IllegalArgumentException, ArithmeticException, ClosedIllegalStateException, BufferOverflowException, InvalidMarshallableException;
}
static class ScalarFieldAccess extends FieldAccess {
public ScalarFieldAccess(Field field) {
super(field);
}
@Override
protected void getValue(Object o, @NotNull BytesOut> write)
throws BufferOverflowException, ClosedIllegalStateException, IllegalAccessException {
Object o2 = field.get(o);
@Nullable String s = o2 == null ? null : o2.toString();
write.writeUtf8(s);
}
@Override
protected void setValue(Object o, @NotNull BytesIn> read)
throws IORuntimeException, BufferUnderflowException, ClosedIllegalStateException, ArithmeticException, IllegalArgumentException, IllegalAccessException {
@Nullable String s = read.readUtf8();
field.set(o, ObjectUtils.convertTo(field.getType(), s));
}
}
static class BytesMarshallableFieldAccess extends FieldAccess {
public BytesMarshallableFieldAccess(Field field) {
super(field);
}
@Override
protected void getValue(Object o, BytesOut> write)
throws BufferUnderflowException, ClosedIllegalStateException, BufferOverflowException, InvalidMarshallableException, IllegalAccessException {
@NotNull BytesMarshallable o2 = (BytesMarshallable) field.get(o);
ValidatableUtil.validate(o2);
assert o2 != null;
o2.writeMarshallable(write);
}
@SuppressWarnings("unchecked")
@Override
protected void setValue(Object o, BytesIn> read)
throws IORuntimeException, BufferUnderflowException, ClosedIllegalStateException, InvalidMarshallableException, IllegalAccessException {
@NotNull BytesMarshallable o2 = (BytesMarshallable) field.get(o);
if (!field.getType().isInstance(o2)) {
o2 = (BytesMarshallable) ObjectUtils.newInstance((Class) field.getType());
field.set(o, o2);
}
assert o2 != null;
o2.readMarshallable(read);
}
}
static class BytesFieldAccess extends FieldAccess {
public BytesFieldAccess(Field field) {
super(field);
}
@Override
protected void getValue(Object o, @NotNull BytesOut> write)
throws ClosedIllegalStateException, BufferOverflowException, IllegalArgumentException, IllegalAccessException {
@NotNull BytesStore, ?> bytes;
bytes = (BytesStore) field.get(o);
if (bytes == null) {
BytesInternal.writeStopBitNeg1(write);
return;
}
long offset = bytes.readPosition();
long length = bytes.readRemaining();
write.writeStopBit(length);
write.write(bytes, offset, length);
}
@Override
protected void setValue(Object o, @NotNull BytesIn> read)
throws IORuntimeException, IllegalArgumentException, ClosedIllegalStateException, ArithmeticException, BufferUnderflowException, BufferOverflowException, IllegalAccessException {
@NotNull Bytes> bytes = (Bytes) field.get(o);
long stopBit = read.readStopBit();
if (stopBit == -1) {
if (bytes != null)
bytes.releaseLast();
field.set(o, null);
return;
}
int length = Maths.toUInt31(stopBit);
@NotNull Bytes> bs;
if (bytes == null) {
bs = Bytes.allocateElasticOnHeap(length);
field.set(o, bs);
} else {
bs = bytes;
}
bs.clear();
read.read(bs, length);
bs.readLimit(length);
}
}
static class ObjectArrayFieldAccess extends FieldAccess {
Class> componentType;
public ObjectArrayFieldAccess(Field field) {
super(field);
componentType = field.getType().getComponentType();
}
@Override
protected void getValue(Object o, BytesOut> write)
throws ClosedIllegalStateException, BufferOverflowException, IllegalArgumentException, BufferUnderflowException, ArithmeticException, InvalidMarshallableException, IllegalAccessException, ThreadingIllegalStateException {
Object[] c = (Object[]) field.get(o);
if (c == null) {
BytesInternal.writeStopBitNeg1(write);
return;
}
int size = c.length;
write.writeStopBit(size);
if (size == 0)
return;
for (int i = 0; i < size; i++)
write.writeObject(componentType, c[i]);
}
@Override
protected void setValue(Object o, BytesIn> read)
throws ClosedIllegalStateException, BufferUnderflowException, IllegalArgumentException, ArithmeticException, BufferOverflowException, InvalidMarshallableException, IllegalAccessException {
Object[] c = (Object[]) field.get(o);
int length = Maths.toInt32(read.readStopBit());
if (length < 0) {
if (c != null)
field.set(o, null);
return;
}
if (c == null) {
c = (Object[]) Array.newInstance(field.getType().getComponentType(), length);
field.set(o, c);
} else if (c.length != length)
field.set(o, c = Arrays.copyOf(c, length));
for (int i = 0; i < length; i++) {
Object o2 = c[i];
if (o2 instanceof BytesMarshallable)
((BytesMarshallable) o2).readMarshallable(read);
else
c[i] = read.readObject(componentType);
}
}
}
static class CollectionFieldAccess extends FieldAccess {
final Supplier collectionSupplier;
@NotNull
private final Class> componentType;
private final Class> type;
public CollectionFieldAccess(@NotNull Field field) {
super(field);
type = field.getType();
if (type == List.class || type == Collection.class)
collectionSupplier = ArrayList::new;
else if (type == SortedSet.class || type == NavigableSet.class)
collectionSupplier = TreeSet::new;
else if (type == Set.class)
collectionSupplier = LinkedHashSet::new;
else
collectionSupplier = () -> uncheckedCast(ObjectUtils.newInstance(type));
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) {
@NotNull ParameterizedType pType = (ParameterizedType) genericType;
Type type0 = pType.getActualTypeArguments()[0];
componentType = extractClass(type0);
} else {
componentType = Object.class;
}
}
@Override
protected void getValue(Object o, BytesOut> write)
throws ClosedIllegalStateException, BufferOverflowException, IllegalArgumentException, BufferUnderflowException, ArithmeticException, InvalidMarshallableException, IllegalAccessException, ThreadingIllegalStateException {
Collection c = (Collection) field.get(o);
if (c == null) {
BytesInternal.writeStopBitNeg1(write);
return;
}
write.writeStopBit(c.size());
if (c.isEmpty())
return;
if (c instanceof RandomAccess && c instanceof List) {
List l = (List) c;
for (int i = 0, size = l.size(); i < size; i++)
write.writeObject(componentType, l.get(i));
} else {
for (Object o2 : c) {
write.writeObject(componentType, o2);
}
}
}
@SuppressWarnings("unchecked")
@Override
protected void setValue(Object o, BytesIn> read)
throws ClosedIllegalStateException, ArithmeticException, IllegalArgumentException, BufferUnderflowException, BufferOverflowException, InvalidMarshallableException, IllegalAccessException {
Collection c = (Collection) field.get(o);
int length = Maths.toInt32(read.readStopBit());
if (length < 0) {
if (c != null)
field.set(o, null);
return;
}
if (c == null)
field.set(o, c = collectionSupplier.get());
else
c.clear();
for (int i = 0; i < length; i++)
c.add(read.readObject(componentType));
}
}
static class MapFieldAccess extends FieldAccess {
final Supplier