All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tech.ytsaurus.client.rows.EntitySkiffSerializer Maven / Gradle / Ivy

The newest version!
package tech.ytsaurus.client.rows;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;

import javax.annotation.Nullable;

import tech.ytsaurus.client.request.Format;
import tech.ytsaurus.core.GUID;
import tech.ytsaurus.core.common.Decimal;
import tech.ytsaurus.core.rows.YsonSerializable;
import tech.ytsaurus.core.tables.TableSchema;
import tech.ytsaurus.core.utils.ClassUtils;
import tech.ytsaurus.lang.NonNullApi;
import tech.ytsaurus.lang.NonNullFields;
import tech.ytsaurus.skiff.SkiffParser;
import tech.ytsaurus.skiff.SkiffSchema;
import tech.ytsaurus.skiff.SkiffSerializer;
import tech.ytsaurus.typeinfo.DecimalType;
import tech.ytsaurus.typeinfo.StructType;
import tech.ytsaurus.typeinfo.TiType;
import tech.ytsaurus.yson.BufferReference;
import tech.ytsaurus.yson.ClosableYsonConsumer;
import tech.ytsaurus.ysontree.YTreeBinarySerializer;
import tech.ytsaurus.ysontree.YTreeNode;
import tech.ytsaurus.ysontree.YTreeNodeUtils;

import static tech.ytsaurus.client.rows.TiTypeUtil.isSimpleType;
import static tech.ytsaurus.client.rows.TiTypeUtil.tableSchemaToStructTiType;
import static tech.ytsaurus.core.utils.ClassUtils.anyOfAnnotationsPresent;
import static tech.ytsaurus.core.utils.ClassUtils.boxArray;
import static tech.ytsaurus.core.utils.ClassUtils.castIntToActualType;
import static tech.ytsaurus.core.utils.ClassUtils.castLongToActualType;
import static tech.ytsaurus.core.utils.ClassUtils.castShortToActualType;
import static tech.ytsaurus.core.utils.ClassUtils.castToType;
import static tech.ytsaurus.core.utils.ClassUtils.getAllDeclaredFields;
import static tech.ytsaurus.core.utils.ClassUtils.getConstructorOrDefaultFor;
import static tech.ytsaurus.core.utils.ClassUtils.getInstanceWithoutArguments;
import static tech.ytsaurus.core.utils.ClassUtils.getTypeDescription;
import static tech.ytsaurus.core.utils.ClassUtils.isAssignableToInt;
import static tech.ytsaurus.core.utils.ClassUtils.isAssignableToLong;
import static tech.ytsaurus.core.utils.ClassUtils.isAssignableToShort;
import static tech.ytsaurus.core.utils.ClassUtils.setFieldsAccessibleToTrue;
import static tech.ytsaurus.core.utils.ClassUtils.unboxArray;

@NonNullApi
@NonNullFields
public class EntitySkiffSerializer {
    private static final byte ZERO_TAG = 0x00;
    private static final byte ONE_TAG = 0x01;
    private static final byte END_TAG = (byte) 0xFF;
    private final Class entityClass;
    private final Map, List> entityFieldsMap = new HashMap<>();
    private final Map, Constructor> objectConstructorMap = new HashMap<>();
    private @Nullable TableSchema entityTableSchema;
    private @Nullable TiType entityTiType;
    private @Nullable SkiffSchema skiffSchema;
    private @Nullable Format format;

    public EntitySkiffSerializer(Class entityClass) {
        if (!anyOfAnnotationsPresent(entityClass, JavaPersistenceApi.entityAnnotations())) {
            throw new IllegalArgumentException("Class must be annotated with @Entity");
        }
        this.entityClass = entityClass;
        try {
            this.entityTableSchema = EntityTableSchemaCreator.create(entityClass);
            this.entityTiType = tableSchemaToStructTiType(entityTableSchema);
            this.skiffSchema = SchemaConverter.toSkiffSchema(entityTableSchema);
            this.format = Format.skiff(skiffSchema, 1);
        } catch (EntityTableSchemaCreator.PrecisionAndScaleNotSpecifiedException ignored) {
        }

        entityFieldsMap.put(entityClass, getEntityFieldDescrs(entityClass));
    }

    public Optional getFormat() {
        return Optional.ofNullable(format);
    }

    public Optional getEntityTableSchema() {
        return Optional.ofNullable(entityTableSchema);
    }

    public void setTableSchema(TableSchema tableSchema) {
        this.entityTableSchema = EntityTableSchemaCreator.create(entityClass, tableSchema);
        this.entityTiType = tableSchemaToStructTiType(entityTableSchema);
        this.skiffSchema = SchemaConverter.toSkiffSchema(entityTableSchema);
        this.format = Format.skiff(skiffSchema, 1);
    }

    public byte[] serialize(T object) {
        if (entityTiType == null) {
            throwNoSchemaException();
        }

        ByteArrayOutputStream byteOS = new ByteArrayOutputStream();
        serialize(object, new SkiffSerializer(byteOS));
        try {
            byteOS.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return byteOS.toByteArray();
    }

    public void serialize(T object, SkiffSerializer serializer) {
        if (entityTiType == null) {
            throwNoSchemaException();
        }
        serializeEntity(object, entityTiType, serializer);
    }

    public Optional deserialize(byte[] objectBytes) {
        return deserialize(new SkiffParser(new ByteArrayInputStream(objectBytes)));
    }

    public Optional deserialize(SkiffParser parser) {
        if (entityTiType == null) {
            throwNoSchemaException();
        }
        return Optional.ofNullable(
                deserializeObject(entityClass, entityTiType, List.of(), parser)
        );
    }

    private  void serializeObject(
            @Nullable ObjectType object,
            TiType tiType,
            SkiffSerializer serializer
    ) {
        boolean isNullable = tiType.isOptional();
        if (object == null) {
            if (!isNullable) {
                throw new NullPointerException(
                        String.format("Field '%s' is non nullable", tiType));
            }
            serializer.serializeByte(ZERO_TAG);
            return;
        }
        if (isNullable) {
            serializer.serializeByte(ONE_TAG);
            tiType = tiType.asOptional().getItem();
        }

        if (isSimpleType(tiType)) {
            serializeSimpleType(object, tiType, serializer);
            return;
        }
        if (tiType.isDecimal()) {
            if (!object.getClass().equals(BigDecimal.class)) {
                throwInvalidSchemeException(null);
            }
            serializeDecimal((BigDecimal) object, tiType.asDecimal(), serializer);
            return;
        }

        serializeComplexObject(object, tiType, serializer);
    }

    private void serializeDecimal(
            BigDecimal value,
            DecimalType decimalType,
            SkiffSerializer serializer
    ) {
        byte[] dataInBigEndian = Decimal.textToBinary(
                value.toPlainString(),
                decimalType.getPrecision(),
                decimalType.getScale()
        );
        dataInBigEndian[0] = (byte) (dataInBigEndian[0] ^ (0x1 << 7));

        for (int i = dataInBigEndian.length - 1; i >= 0; i--) {
            serializer.serializeByte(dataInBigEndian[i]);
        }
    }

    private  void serializeSimpleType(
            SimpleType object,
            TiType tiType,
            SkiffSerializer serializer
    ) {
        try {
            if (tiType.isInt8()) {
                serializer.serializeByte((byte) object);
            } else if (tiType.isInt16()) {
                serializeInt16(object, serializer);
            } else if (tiType.isInt32()) {
                serializeInt32(object, serializer);
            } else if (tiType.isInt64()) {
                serializeInt64(object, serializer);
            } else if (tiType.isUint8()) {
                serializer.serializeUint8((long) object);
            } else if (tiType.isUint16()) {
                serializer.serializeUint16((long) object);
            } else if (tiType.isUint32()) {
                serializer.serializeUint32((long) object);
            } else if (tiType.isUint64()) {
                serializer.serializeUint64((long) object);
            } else if (tiType.isDouble()) {
                serializer.serializeDouble((double) object);
            } else if (tiType.isBool()) {
                serializer.serializeBoolean((boolean) object);
            } else if (tiType.isUtf8()) {
                serializeUtf8(object, serializer);
            } else if (tiType.isString()) {
                serializeString(object, serializer);
            } else if (tiType.isUuid()) {
                serializer.serializeGuid((GUID) object);
            } else if (tiType.isTimestamp()) {
                serializer.serializeTimestamp((Instant) object);
            } else if (tiType.isYson()) {
                serializeYson(object, serializer);
            } else {
                throw new IllegalArgumentException(
                        String.format("Type '%s' is not supported", tiType));
            }
            return;
        } catch (ClassCastException e) {
            throwInvalidSchemeException(e);
        }
        throw new IllegalStateException();
    }

    private  void serializeInt16(
            Int16Type object,
            SkiffSerializer serializer
    ) {
        if (isAssignableToShort(object.getClass())) {
            serializer.serializeShort(((Number) object).shortValue());
            return;
        }
        throwIncorrectFieldTypeException("int16", object.getClass());
    }

    private  void serializeInt32(
            Int32Type object,
            SkiffSerializer serializer
    ) {
        if (isAssignableToInt(object.getClass())) {
            serializer.serializeInt(((Number) object).intValue());
            return;
        }
        throwIncorrectFieldTypeException("int32", object.getClass());
    }

    private  void serializeInt64(
            Int64Type object,
            SkiffSerializer serializer
    ) {
        long l = 0;
        if (isAssignableToLong(object.getClass())) {
            l = ((Number) object).longValue();
        } else if (object.getClass().equals(Instant.class)) {
            l = ((Instant) object).toEpochMilli();
        } else {
            throwIncorrectFieldTypeException("int64", object.getClass());
        }
        serializer.serializeLong(l);
    }

    private  void serializeUtf8(
            Utf8Type object,
            SkiffSerializer serializer
    ) {
        String str = null;
        if (object.getClass().equals(String.class)) {
            str = (String) object;
        } else if (Enum.class.isAssignableFrom(object.getClass())) {
            str = ((Enum) object).name();
        } else {
            throwIncorrectFieldTypeException("utf8", object.getClass());
        }
        serializer.serializeUtf8(str);
    }

    private  void serializeString(
            StringType object,
            SkiffSerializer serializer
    ) {
        if (object.getClass().equals(byte[].class)) {
            serializer.serializeString((byte[]) object);
        } else if (object.getClass().equals(String.class) || Enum.class.isAssignableFrom(object.getClass())) {
            serializeUtf8(object, serializer);
        } else {
            throwIncorrectFieldTypeException("string", object.getClass());
        }
    }

    private  void serializeYson(
            YsonType object,
            SkiffSerializer serializer
    ) {
        var byteOutputStreamForYson = new ByteArrayOutputStream();
        ClosableYsonConsumer consumer = YTreeBinarySerializer.getSerializer(byteOutputStreamForYson);
        if (YTreeNode.class.isAssignableFrom(object.getClass())) {
            YTreeNodeUtils.walk((YTreeNode) object, consumer, true);
        } else if (YsonSerializable.class.isAssignableFrom(object.getClass())) {
            ((YsonSerializable) object).serialize(consumer);
        } else {
            throwIncorrectFieldTypeException("yson", object.getClass());
        }
        consumer.close();
        byte[] bytes = byteOutputStreamForYson.toByteArray();
        serializer.serializeString(bytes);
    }

    private  void serializeComplexObject(
            ObjectType object,
            TiType objectTiType,
            SkiffSerializer serializer
    ) {
        final Class clazz = object.getClass();
        if (Collection.class.isAssignableFrom(clazz)) {
            serializeCollection(object, objectTiType, serializer);
            return;
        }
        if (Map.class.isAssignableFrom(clazz)) {
            serializeMap(object, objectTiType, serializer);
            return;
        }
        if (clazz.isArray()) {
            serializeArray(object, objectTiType, serializer);
            return;
        }

        serializeEntity(object, objectTiType, serializer);
    }

    private static List getEntityFieldDescrs(Class entityClass) {
        var declaredFields = getAllDeclaredFields(entityClass);
        setFieldsAccessibleToTrue(declaredFields);
        return EntityFieldDescr.of(declaredFields);
    }

    private  void serializeEntity(
            ObjectType object,
            TiType structTiType,
            SkiffSerializer serializer
    ) {
        if (!structTiType.isStruct()) {
            throwInvalidSchemeException(null);
        }

        serializeEntity(object, structTiType.asStruct().getMembers().iterator(), serializer);
    }

    private  void serializeEntity(
            ObjectType object,
            Iterator structMembersIterator,
            SkiffSerializer serializer
    ) {
        if (!structMembersIterator.hasNext()) {
            throwInvalidSchemeException(null);
        }

        var fieldDescriptions = entityFieldsMap.computeIfAbsent(
                object.getClass(), EntitySkiffSerializer::getEntityFieldDescrs);
        for (var fieldDescr : fieldDescriptions) {
            if (fieldDescr.isTransient()) {
                continue;
            }
            try {
                if (fieldDescr.isEmbeddable()) {
                    serializeEntity(fieldDescr.getField().get(object), structMembersIterator, serializer);
                } else {
                    serializeObject(
                            fieldDescr.getField().get(object),
                            structMembersIterator.next().getType(),
                            serializer
                    );
                }
            } catch (IllegalAccessException e) {
                throwInvalidSchemeException(e);
            }
        }

    }

    private  void serializeCollection(
            CollectionType object,
            TiType tiType,
            SkiffSerializer serializer
    ) {
        if (!tiType.isList()) {
            throwInvalidSchemeException(null);
        }

        Collection collection = castToType(object);
        var elemTiType = tiType.asList().getItem();
        for (var elem : collection) {
            serializer.serializeByte(ZERO_TAG);
            serializeObject(elem, elemTiType, serializer);
        }
        serializer.serializeByte(END_TAG);
    }

    private  void serializeMap(
            MapType object,
            TiType tiType,
            SkiffSerializer serializer
    ) {
        if (!tiType.isDict()) {
            throwInvalidSchemeException(null);
        }

        Map map = castToType(object);
        var keyTiType = tiType.asDict().getKey();
        var valueTiType = tiType.asDict().getValue();
        for (var entry : map.entrySet()) {
            serializer.serializeByte(ZERO_TAG);
            serializeObject(entry.getKey(), keyTiType, serializer);
            serializeObject(entry.getValue(), valueTiType, serializer);
        }
        serializer.serializeByte(END_TAG);
    }

    private  void serializeArray(
            ArrayType object,
            TiType tiType,
            SkiffSerializer serializer
    ) {
        if (!tiType.isList()) {
            throwInvalidSchemeException(null);
        }

        Class elementClass = object.getClass().getComponentType();
        ElemType[] list = elementClass.isPrimitive() ?
                boxArray(object, elementClass) :
                castToType(object);
        var elemTiType = tiType.asList().getItem();
        for (var elem : list) {
            serializer.serializeByte(ZERO_TAG);
            serializeObject(elem, elemTiType, serializer);
        }
        serializer.serializeByte(END_TAG);
    }

    private  @Nullable ObjectType deserializeObject(
            Class clazz,
            TiType tiType,
            List genericTypeParameters,
            SkiffParser parser
    ) {
        if (tiType.isOptional()) {
            if (parser.parseVariant8Tag() == ZERO_TAG) {
                return null;
            }
            tiType = tiType.asOptional().getItem();
        }

        if (isSimpleType(tiType)) {
            return deserializeSimpleType(clazz, tiType, parser);
        }
        if (tiType.isDecimal()) {
            if (!clazz.equals(BigDecimal.class)) {
                throwInvalidSchemeException(null);
            }
            return castToType(deserializeDecimal(tiType.asDecimal(), parser));
        }

        return deserializeComplexObject(clazz, tiType, genericTypeParameters, parser);
    }

    private BigDecimal deserializeDecimal(DecimalType decimalType, SkiffParser parser) {
        byte[] binaryDecimal = parser.getDataInBigEndian(
                getBinaryDecimalLength(decimalType)
        );
        binaryDecimal[0] = (byte) (binaryDecimal[0] ^ (0x1 << 7));
        String textDecimal = Decimal.binaryToText(
                binaryDecimal,
                decimalType.getPrecision(),
                decimalType.getScale()
        );

        if (textDecimal.equals("nan") || textDecimal.equals("inf") || textDecimal.equals("-inf")) {
            throw new IllegalArgumentException(String.format(
                    "YT Decimal value '%s' is not supported by Java BigDecimal", textDecimal));
        }

        return new BigDecimal(textDecimal);
    }

    private int getBinaryDecimalLength(TiType tiType) {
        int precision = tiType.asDecimal().getPrecision();
        if (precision <= 9) {
            return 4;
        }
        if (precision <= 18) {
            return 8;
        }
        return 16;
    }

    private  ObjectType deserializeComplexObject(
            Class clazz,
            TiType tiType,
            List genericTypeParameters,
            SkiffParser parser
    ) {
        if (List.class.isAssignableFrom(clazz)) {
            return castToType(deserializeCollection(
                    clazz,
                    genericTypeParameters.get(0),
                    tiType,
                    ArrayList.class,
                    parser
            ));
        }
        if (Set.class.isAssignableFrom(clazz)) {
            return castToType(deserializeCollection(
                    clazz,
                    genericTypeParameters.get(0),
                    tiType,
                    HashSet.class,
                    parser
            ));
        }
        if (Queue.class.isAssignableFrom(clazz)) {
            return castToType(deserializeCollection(
                    clazz,
                    genericTypeParameters.get(0),
                    tiType,
                    LinkedList.class,
                    parser
            ));
        }
        if (Map.class.isAssignableFrom(clazz)) {
            return castToType(deserializeMap(
                    clazz,
                    genericTypeParameters.get(0),
                    genericTypeParameters.get(1),
                    tiType,
                    parser
            ));
        }
        if (clazz.isArray()) {
            return deserializeArray(clazz, tiType, parser);
        }

        return deserializeEntity(clazz, tiType, parser);
    }

    private  ObjectType deserializeEntity(
            Class clazz,
            TiType tiType,
            SkiffParser parser
    ) {
        if (!tiType.isStruct()) {
            throwInvalidSchemeException(null);
        }

        return deserializeEntity(clazz, tiType.asStruct().getMembers().iterator(), parser);
    }

    private  ObjectType deserializeEntity(
            Class clazz,
            Iterator structMembersIterator,
            SkiffParser parser
    ) {
        if (!structMembersIterator.hasNext()) {
            throwInvalidSchemeException(null);
        }

        var defaultConstructor = objectConstructorMap.computeIfAbsent(clazz, ClassUtils::getEmptyConstructor);

        ObjectType object = getInstanceWithoutArguments(defaultConstructor);

        var fieldDescriptions = entityFieldsMap.computeIfAbsent(clazz, EntitySkiffSerializer::getEntityFieldDescrs);
        for (var fieldDescr : fieldDescriptions) {
            if (fieldDescr.isTransient()) {
                continue;
            }
            try {
                Object value;
                if (fieldDescr.isEmbeddable()) {
                    value = deserializeEntity(fieldDescr.getField().getType(), structMembersIterator, parser);
                } else {
                    value = deserializeObject(
                            castToType(fieldDescr.getField().getType()),
                            structMembersIterator.next().getType(),
                            fieldDescr.getTypeParameters(),
                            parser
                    );
                }
                fieldDescr.getField().set(object, value);
            } catch (IllegalAccessException e) {
                throwInvalidSchemeException(e);
            }
        }
        return object;
    }

    private  Collection deserializeCollection(
            Class clazz,
            Type elementType,
            TiType tiType,
            Class defaultClass,
            SkiffParser parser
    ) {
        if (!tiType.isList()) {
            throwInvalidSchemeException(null);
        }

        var collectionConstructor = objectConstructorMap.computeIfAbsent(
                clazz,
                getConstructorOrDefaultFor(defaultClass)
        );

        Collection collection = getInstanceWithoutArguments(collectionConstructor);
        var elementTypeDescr = getTypeDescription(elementType);
        while (parser.parseInt8() != END_TAG) {
            collection.add(castToType(
                            deserializeObject(
                                    elementTypeDescr.getTypeClass(),
                                    tiType.asList().getItem(),
                                    elementTypeDescr.getTypeParameters(),
                                    parser
                            )
                    )
            );
        }
        return collection;
    }


    private  Map deserializeMap(
            Class clazz,
            Type keyType,
            Type valueType,
            TiType tiType,
            SkiffParser parser
    ) {
        if (!tiType.isDict()) {
            throwInvalidSchemeException(null);
        }

        var mapConstructor = objectConstructorMap.computeIfAbsent(
                clazz,
                getConstructorOrDefaultFor(HashMap.class)
        );

        Map map = getInstanceWithoutArguments(mapConstructor);
        var keyTypeDescr = getTypeDescription(keyType);
        var valueTypeDescr = getTypeDescription(valueType);
        while (parser.parseInt8() != END_TAG) {
            map.put(castToType(deserializeObject(
                            keyTypeDescr.getTypeClass(),
                            tiType.asDict().getKey(),
                            keyTypeDescr.getTypeParameters(),
                            parser
                    )),
                    castToType(deserializeObject(
                            valueTypeDescr.getTypeClass(),
                            tiType.asDict().getValue(),
                            valueTypeDescr.getTypeParameters(),
                            parser
                    ))
            );
        }
        return map;
    }

    private  ArrayType deserializeArray(
            Class clazz,
            TiType tiType,
            SkiffParser parser
    ) {
        var elementClass = clazz.getComponentType();
        List list = castToType(deserializeCollection(
                clazz, elementClass, tiType, ArrayList.class, parser
        ));
        Object arrayObject = Array.newInstance(elementClass, list.size());
        ElemType[] array = elementClass.isPrimitive() ?
                boxArray(arrayObject, elementClass) :
                castToType(arrayObject);
        for (int i = 0; i < list.size(); i++) {
            array[i] = list.get(i);
        }
        if (elementClass.isPrimitive()) {
            return unboxArray(array, array.getClass().getComponentType());
        }
        return castToType(array);
    }

    private  SimpleType deserializeSimpleType(
            Class clazz,
            TiType tiType,
            SkiffParser parser
    ) {
        try {
            if (tiType.isInt8()) {
                return castToType(parser.parseInt8());
            } else if (tiType.isInt16()) {
                return deserializeInt16(clazz, parser);
            } else if (tiType.isInt32()) {
                return deserializeInt32(clazz, parser);
            } else if (tiType.isInt64()) {
                return deserializeInt64(clazz, parser);
            } else if (tiType.isUint8()) {
                return castToType(parser.parseUint8());
            } else if (tiType.isUint16()) {
                return castToType(parser.parseUint16());
            } else if (tiType.isUint32()) {
                return castToType(parser.parseUint32());
            } else if (tiType.isUint64()) {
                return castToType(parser.parseUint64());
            } else if (tiType.isDouble()) {
                return castToType(parser.parseDouble());
            } else if (tiType.isBool()) {
                return castToType(parser.parseBoolean());
            } else if (tiType.isUtf8()) {
                return castToType(deserializeUtf8(clazz, parser));
            } else if (tiType.isString()) {
                return deserializeString(clazz, parser);
            } else if (tiType.isUuid()) {
                return castToType(deserializeGuid(parser));
            } else if (tiType.isTimestamp()) {
                return castToType(deserializeTimestamp(parser));
            } else if (tiType.isYson()) {
                return castToType(deserializeYson(clazz, parser));
            }
            throw new IllegalArgumentException(
                    String.format("Type '%s' is not supported", tiType));
        } catch (ClassCastException e) {
            throwInvalidSchemeException(e);
        }
        throw new IllegalStateException();
    }

    private  Int16Type deserializeInt16(Class clazz, SkiffParser parser) {
        short int16 = parser.parseInt16();
        if (isAssignableToShort(clazz)) {
            return castShortToActualType(int16, clazz);
        }
        throwIncorrectFieldTypeException("int16", clazz);
        return null;
    }

    private  Int32Type deserializeInt32(Class clazz, SkiffParser parser) {
        int int32 = parser.parseInt32();
        if (isAssignableToInt(clazz)) {
            return castIntToActualType(int32, clazz);
        }
        throwIncorrectFieldTypeException("int32", clazz);
        return null;
    }

    private  Int64Type deserializeInt64(Class clazz, SkiffParser parser) {
        long int64 = parser.parseInt64();
        if (isAssignableToLong(clazz)) {
            return castLongToActualType(int64, clazz);
        }
        if (clazz.equals(Instant.class)) {
            return castToType(Instant.ofEpochMilli(int64));
        }
        throwIncorrectFieldTypeException("int64", clazz);
        return null;
    }

    private  Utf8Type deserializeUtf8(Class clazz, SkiffParser parser) {
        BufferReference ref = parser.parseString32();
        String str = new String(ref.getBuffer(), ref.getOffset(), ref.getLength(), StandardCharsets.UTF_8);
        if (clazz.equals(String.class)) {
            return castToType(str);
        }
        if (Enum.class.isAssignableFrom(clazz)) {
            return castToType(deserializeEnum(clazz, str));
        }
        throwIncorrectFieldTypeException("utf8", clazz);
        return null;
    }

    private > E deserializeEnum(Class clazz, String value) {
        return Enum.valueOf(castToType(clazz), value);
    }

    private  StringType deserializeString(Class clazz, SkiffParser parser) {
        if (clazz.equals(byte[].class)) {
            BufferReference ref = parser.parseString32();
            return castToType(
                    Arrays.copyOfRange(ref.getBuffer(), ref.getOffset(), ref.getOffset() + ref.getLength())
            );
        }
        if (clazz.equals(String.class) || Enum.class.isAssignableFrom(clazz)) {
            return deserializeUtf8(clazz, parser);
        }
        throwIncorrectFieldTypeException("string", clazz);
        return null;
    }

    private GUID deserializeGuid(SkiffParser parser) {
        if (parser.parseInt32() != 16) {
            throw new IllegalArgumentException("Length of UUID must be exactly 16 bytes");
        }
        return new GUID(parser.parseInt64(), parser.parseInt64());
    }

    private Instant deserializeTimestamp(SkiffParser parser) {
        return Instant.ofEpochMilli(parser.parseUint64());
    }

    private  YsonType deserializeYson(Class clazz, SkiffParser parser) {
        BufferReference ref = parser.parseYson32();
        ByteArrayInputStream inputStream = new ByteArrayInputStream(ref.getBuffer(), ref.getOffset(), ref.getLength());
        YTreeNode node = YTreeBinarySerializer.deserialize(inputStream);
        if (YTreeNode.class.isAssignableFrom(clazz)) {
            return castToType(node);
        }
        if (YsonSerializable.class.isAssignableFrom(clazz)) {
            var constructor = objectConstructorMap.computeIfAbsent(clazz, ClassUtils::getEmptyConstructor);
            YsonSerializable ysonSerializable = getInstanceWithoutArguments(constructor);
            ysonSerializable.deserialize(node);
            return castToType(ysonSerializable);
        }
        throwIncorrectFieldTypeException("yson", clazz);
        return null;
    }

    private void throwInvalidSchemeException(@Nullable Exception e) {
        throw new IllegalStateException("Scheme does not correspond to object", e);
    }

    private void throwIncorrectFieldTypeException(String typeName, Class clazz) {
        throw new RuntimeException(
                String.format("Incorrect field type for '%s': %s", typeName, clazz.getCanonicalName())
        );
    }

    private void throwNoSchemaException() {
        throw new IllegalStateException("Cannot create or get table schema");
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy