| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| namespace Apache.Fory; |
| |
| public sealed class DynamicAnyObjectSerializer : Serializer<object?> |
| { |
| public override object? DefaultValue => null; |
| |
| public override void WriteData(WriteContext context, in object? value, bool hasGenerics) |
| { |
| if (value is null) |
| { |
| return; |
| } |
| |
| DynamicAnyCodec.WriteAnyPayload(value!, context, hasGenerics); |
| } |
| |
| public override object? ReadData(ReadContext context) |
| { |
| TypeInfo? typeInfo = context.GetReadTypeInfo(typeof(object)); |
| if (typeInfo is null) |
| { |
| throw new InvalidDataException("dynamic Any value requires type info"); |
| } |
| |
| return context.TypeResolver.ReadAnyValue(typeInfo, context); |
| } |
| |
| public override void Write(WriteContext context, in object? value, RefMode refMode, bool writeTypeInfo, bool hasGenerics) |
| { |
| if (refMode != RefMode.None) |
| { |
| if (value is null) |
| { |
| context.Writer.WriteInt8((sbyte)RefFlag.Null); |
| return; |
| } |
| |
| bool wroteTrackingRefFlag = false; |
| if (refMode == RefMode.Tracking && AnyValueIsRefType(value!, context.TypeResolver)) |
| { |
| if (context.RefWriter.TryWriteRef(context.Writer, value!)) |
| { |
| return; |
| } |
| |
| wroteTrackingRefFlag = true; |
| } |
| |
| if (!wroteTrackingRefFlag) |
| { |
| context.Writer.WriteInt8((sbyte)RefFlag.NotNullValue); |
| } |
| } |
| |
| if (writeTypeInfo) |
| { |
| DynamicAnyCodec.WriteAnyTypeInfo(value!, context); |
| } |
| |
| WriteData(context, value, hasGenerics); |
| } |
| |
| public override object? Read(ReadContext context, RefMode refMode, bool readTypeInfo) |
| { |
| if (refMode != RefMode.None) |
| { |
| RefFlag flag = context.RefReader.ReadRefFlag(context.Reader); |
| switch (flag) |
| { |
| case RefFlag.Null: |
| return null; |
| case RefFlag.Ref: |
| { |
| uint refId = context.RefReader.ReadRefId(context.Reader); |
| return context.RefReader.GetRefValue(refId); |
| } |
| case RefFlag.RefValue: |
| { |
| uint reservedRefId = context.RefReader.ReserveRefId(); |
| context.SetReservedRefId(reservedRefId); |
| try |
| { |
| object? value = ReadNonNullDynamicAny(context, readTypeInfo); |
| context.StoreRef(value); |
| return value; |
| } |
| finally |
| { |
| context.ClearReservedRefId(); |
| } |
| } |
| case RefFlag.NotNullValue: |
| break; |
| default: |
| throw new RefException($"invalid ref flag {(sbyte)flag}"); |
| } |
| } |
| |
| return ReadNonNullDynamicAny(context, readTypeInfo); |
| } |
| |
| private static bool AnyValueIsRefType(object value, TypeResolver typeResolver) |
| { |
| TypeInfo typeInfo = typeResolver.GetTypeInfo(value.GetType()); |
| return typeInfo.IsRefType; |
| } |
| |
| private static void ReadAnyTypeInfo(ReadContext context) |
| { |
| TypeInfo typeInfo = context.TypeResolver.ReadAnyTypeInfo(context); |
| context.SetReadTypeInfo(typeof(object), typeInfo); |
| } |
| |
| private object? ReadNonNullDynamicAny(ReadContext context, bool readTypeInfo) |
| { |
| context.IncreaseReadDepth(); |
| bool loadedDynamicTypeInfo = false; |
| try |
| { |
| if (readTypeInfo) |
| { |
| ReadAnyTypeInfo(context); |
| loadedDynamicTypeInfo = true; |
| } |
| |
| return ReadData(context); |
| } |
| finally |
| { |
| if (loadedDynamicTypeInfo) |
| { |
| context.ClearReadTypeInfo(typeof(object)); |
| } |
| |
| context.DecreaseReadDepth(); |
| } |
| } |
| } |
| |
| public static class DynamicAnyCodec |
| { |
| internal static void WriteAnyTypeInfo(object value, WriteContext context) |
| { |
| if (DynamicContainerCodec.TryGetTypeId(value, out TypeId containerTypeId)) |
| { |
| context.Writer.WriteUInt8((byte)containerTypeId); |
| return; |
| } |
| |
| if (TryWriteKnownTypeInfo(value, context)) |
| { |
| return; |
| } |
| |
| TypeInfo typeInfo = context.TypeResolver.GetTypeInfo(value.GetType()); |
| context.TypeResolver.WriteTypeInfo(typeInfo, context); |
| } |
| |
| public static object? CastAnyDynamicValue(object? value, Type targetType) |
| { |
| if (value is null) |
| { |
| if (targetType == typeof(object)) |
| { |
| return null; |
| } |
| |
| if (!targetType.IsValueType || Nullable.GetUnderlyingType(targetType) is not null) |
| { |
| return null; |
| } |
| |
| throw new InvalidDataException($"cannot cast null dynamic Any value to non-nullable {targetType}"); |
| } |
| |
| if (targetType.IsInstanceOfType(value)) |
| { |
| return value; |
| } |
| |
| throw new InvalidDataException($"cannot cast dynamic Any value to {targetType}"); |
| } |
| |
| public static void WriteAny(WriteContext context, object? value, RefMode refMode, bool writeTypeInfo = true, bool hasGenerics = false) |
| { |
| context.TypeResolver.GetSerializer<object?>().Write(context, value, refMode, writeTypeInfo, hasGenerics); |
| } |
| |
| public static object? ReadAny(ReadContext context, RefMode refMode, bool readTypeInfo = true) |
| { |
| return context.TypeResolver.GetSerializer<object?>().Read(context, refMode, readTypeInfo); |
| } |
| |
| internal static void WriteAnyPayload(object value, WriteContext context, bool hasGenerics) |
| { |
| if (DynamicContainerCodec.TryWritePayload(value, context, hasGenerics)) |
| { |
| return; |
| } |
| |
| if (TryWriteKnownPayload(value, context)) |
| { |
| return; |
| } |
| |
| TypeInfo typeInfo = context.TypeResolver.GetTypeInfo(value.GetType()); |
| context.TypeResolver.WriteDataObject(typeInfo, context, value, hasGenerics); |
| } |
| |
| private static bool TryWriteKnownTypeInfo(object value, WriteContext context) |
| { |
| switch (value) |
| { |
| case bool: |
| context.Writer.WriteUInt8((byte)TypeId.Bool); |
| return true; |
| case sbyte: |
| context.Writer.WriteUInt8((byte)TypeId.Int8); |
| return true; |
| case short: |
| context.Writer.WriteUInt8((byte)TypeId.Int16); |
| return true; |
| case int: |
| context.Writer.WriteUInt8((byte)TypeId.VarInt32); |
| return true; |
| case long: |
| context.Writer.WriteUInt8((byte)TypeId.VarInt64); |
| return true; |
| case byte: |
| context.Writer.WriteUInt8((byte)TypeId.UInt8); |
| return true; |
| case ushort: |
| context.Writer.WriteUInt8((byte)TypeId.UInt16); |
| return true; |
| case uint: |
| context.Writer.WriteUInt8((byte)TypeId.VarUInt32); |
| return true; |
| case ulong: |
| context.Writer.WriteUInt8((byte)TypeId.VarUInt64); |
| return true; |
| case float: |
| context.Writer.WriteUInt8((byte)TypeId.Float32); |
| return true; |
| case double: |
| context.Writer.WriteUInt8((byte)TypeId.Float64); |
| return true; |
| case string: |
| context.Writer.WriteUInt8((byte)TypeId.String); |
| return true; |
| case byte[]: |
| context.Writer.WriteUInt8((byte)TypeId.Binary); |
| return true; |
| case bool[]: |
| context.Writer.WriteUInt8((byte)TypeId.BoolArray); |
| return true; |
| case sbyte[]: |
| context.Writer.WriteUInt8((byte)TypeId.Int8Array); |
| return true; |
| case short[]: |
| context.Writer.WriteUInt8((byte)TypeId.Int16Array); |
| return true; |
| case int[]: |
| context.Writer.WriteUInt8((byte)TypeId.Int32Array); |
| return true; |
| case long[]: |
| context.Writer.WriteUInt8((byte)TypeId.Int64Array); |
| return true; |
| case ushort[]: |
| context.Writer.WriteUInt8((byte)TypeId.UInt16Array); |
| return true; |
| case uint[]: |
| context.Writer.WriteUInt8((byte)TypeId.UInt32Array); |
| return true; |
| case ulong[]: |
| context.Writer.WriteUInt8((byte)TypeId.UInt64Array); |
| return true; |
| case float[]: |
| context.Writer.WriteUInt8((byte)TypeId.Float32Array); |
| return true; |
| case double[]: |
| context.Writer.WriteUInt8((byte)TypeId.Float64Array); |
| return true; |
| case DateOnly: |
| context.Writer.WriteUInt8((byte)TypeId.Date); |
| return true; |
| case DateTimeOffset: |
| case DateTime: |
| context.Writer.WriteUInt8((byte)TypeId.Timestamp); |
| return true; |
| case TimeSpan: |
| context.Writer.WriteUInt8((byte)TypeId.Duration); |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| private static bool TryWriteKnownPayload(object value, WriteContext context) |
| { |
| switch (value) |
| { |
| case bool v: |
| context.Writer.WriteUInt8(v ? (byte)1 : (byte)0); |
| return true; |
| case sbyte v: |
| context.Writer.WriteInt8(v); |
| return true; |
| case short v: |
| context.Writer.WriteInt16(v); |
| return true; |
| case int v: |
| context.Writer.WriteVarInt32(v); |
| return true; |
| case long v: |
| context.Writer.WriteVarInt64(v); |
| return true; |
| case byte v: |
| context.Writer.WriteUInt8(v); |
| return true; |
| case ushort v: |
| context.Writer.WriteUInt16(v); |
| return true; |
| case uint v: |
| context.Writer.WriteVarUInt32(v); |
| return true; |
| case ulong v: |
| context.Writer.WriteVarUInt64(v); |
| return true; |
| case float v: |
| context.Writer.WriteFloat32(v); |
| return true; |
| case double v: |
| context.Writer.WriteFloat64(v); |
| return true; |
| case string v: |
| StringSerializer.WriteString(context, v); |
| return true; |
| case DateOnly v: |
| TimeCodec.WriteDate(context, v); |
| return true; |
| case DateTimeOffset v: |
| TimeCodec.WriteTimestamp(context, v); |
| return true; |
| case DateTime v: |
| TimeCodec.WriteTimestamp(context, TimeCodec.ToDateTimeOffset(v)); |
| return true; |
| case TimeSpan v: |
| TimeCodec.WriteDuration(context, v); |
| return true; |
| case byte[] v: |
| context.Writer.WriteVarUInt32((uint)v.Length); |
| context.Writer.WriteBytes(v); |
| return true; |
| case bool[] v: |
| context.Writer.WriteVarUInt32((uint)v.Length); |
| for (int i = 0; i < v.Length; i++) |
| { |
| context.Writer.WriteUInt8(v[i] ? (byte)1 : (byte)0); |
| } |
| return true; |
| case sbyte[] v: |
| context.Writer.WriteVarUInt32((uint)v.Length); |
| for (int i = 0; i < v.Length; i++) |
| { |
| context.Writer.WriteInt8(v[i]); |
| } |
| return true; |
| case short[] v: |
| context.Writer.WriteVarUInt32((uint)(v.Length * 2)); |
| for (int i = 0; i < v.Length; i++) |
| { |
| context.Writer.WriteInt16(v[i]); |
| } |
| return true; |
| case int[] v: |
| context.Writer.WriteVarUInt32((uint)(v.Length * 4)); |
| for (int i = 0; i < v.Length; i++) |
| { |
| context.Writer.WriteInt32(v[i]); |
| } |
| return true; |
| case long[] v: |
| context.Writer.WriteVarUInt32((uint)(v.Length * 8)); |
| for (int i = 0; i < v.Length; i++) |
| { |
| context.Writer.WriteInt64(v[i]); |
| } |
| return true; |
| case ushort[] v: |
| context.Writer.WriteVarUInt32((uint)(v.Length * 2)); |
| for (int i = 0; i < v.Length; i++) |
| { |
| context.Writer.WriteUInt16(v[i]); |
| } |
| return true; |
| case uint[] v: |
| context.Writer.WriteVarUInt32((uint)(v.Length * 4)); |
| for (int i = 0; i < v.Length; i++) |
| { |
| context.Writer.WriteUInt32(v[i]); |
| } |
| return true; |
| case ulong[] v: |
| context.Writer.WriteVarUInt32((uint)(v.Length * 8)); |
| for (int i = 0; i < v.Length; i++) |
| { |
| context.Writer.WriteUInt64(v[i]); |
| } |
| return true; |
| case float[] v: |
| context.Writer.WriteVarUInt32((uint)(v.Length * 4)); |
| for (int i = 0; i < v.Length; i++) |
| { |
| context.Writer.WriteFloat32(v[i]); |
| } |
| return true; |
| case double[] v: |
| context.Writer.WriteVarUInt32((uint)(v.Length * 8)); |
| for (int i = 0; i < v.Length; i++) |
| { |
| context.Writer.WriteFloat64(v[i]); |
| } |
| return true; |
| default: |
| return false; |
| } |
| } |
| } |