// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; using EpicGames.Core; using EpicGames.Serialization.Converters; namespace EpicGames.Serialization { /// /// Attribute declaring the converter to use for a type /// /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property)] public sealed class CbConverterAttribute(Type converterType) : Attribute { /// /// The converter type /// public Type ConverterType { get; } = converterType; } /// /// Base class for all converters. /// public abstract partial class CbConverter { /// /// Reads an object from a field /// /// /// public abstract object? ReadObject(CbField field); /// /// Writes an object to a field /// /// /// public abstract void WriteObject(CbWriter writer, object? value); /// /// Writes an object to a named field, if not equal to the default value /// /// /// /// public abstract void WriteNamedObject(CbWriter writer, CbFieldName name, object? value); } /// /// Converter for a particular type /// public abstract class CbConverter : CbConverter { /// public sealed override object? ReadObject(CbField field) => Read(field); /// public sealed override void WriteObject(CbWriter writer, object? value) => Write(writer, (T)value!); /// public sealed override void WriteNamedObject(CbWriter writer, CbFieldName name, object? value) => WriteNamed(writer, name, (T)value!); /// /// Reads an object from a field /// /// /// public abstract T Read(CbField field); /// /// Writes an object to a field /// /// /// public abstract void Write(CbWriter writer, T value); /// /// Writes an object to a named field, if not equal to the default value /// /// /// /// public abstract void WriteNamed(CbWriter writer, CbFieldName name, T value); } /// /// Interface for obtaining static methods that can be invoked directly /// public interface ICbConverterMethods { /// /// Method with the signature CbField -> T /// public MethodInfo ReadMethod { get; } /// /// Method with the signature CbWriter, T -> void /// public MethodInfo WriteMethod { get; } /// /// Method with the signature CbWriter, CbFieldName, T -> void /// public MethodInfo WriteNamedMethod { get; } } /// /// Helper class for wrapping regular converters in a ICbConverterMethods interface /// public static class CbConverterMethods { class CbConverterMethodsWrapper : ICbConverterMethods where TConverter : CbConverter { static TConverter s_staticConverter = null!; static T Read(CbField field) => s_staticConverter.Read(field); static void Write(CbWriter writer, T value) => s_staticConverter.Write(writer, value); static void WriteNamed(CbWriter writer, CbFieldName name, T value) => s_staticConverter.WriteNamed(writer, name, value); public MethodInfo ReadMethod { get; } = GetMethodInfo(() => Read(null!)); public MethodInfo WriteMethod { get; } = GetMethodInfo(() => Write(null!, default!)); public MethodInfo WriteNamedMethod { get; } = GetMethodInfo(() => WriteNamed(null!, default, default!)); public CbConverterMethodsWrapper(TConverter converter) { s_staticConverter = converter; } static MethodInfo GetMethodInfo(Expression expr) { return ((MethodCallExpression)expr.Body).Method; } } static readonly Dictionary s_propertyToMethods = []; static readonly Dictionary s_typeToMethods = []; static ICbConverterMethods CreateWrapper(Type type, CbConverter converter) { Type converterMethodsType = typeof(CbConverterMethodsWrapper<,>).MakeGenericType(converter.GetType(), type); return (ICbConverterMethods)Activator.CreateInstance(converterMethodsType, [converter])!; } /// /// Gets a interface for the given property /// /// /// public static ICbConverterMethods Get(PropertyInfo property) { ICbConverterMethods? methods; if (!s_propertyToMethods.TryGetValue(property, out methods)) { CbConverter converter = CbConverter.GetConverter(property); methods = (converter as ICbConverterMethods) ?? CreateWrapper(property.PropertyType, converter); s_propertyToMethods.Add(property, methods); } return methods; } /// /// Gets a interface for the given type /// /// /// public static ICbConverterMethods Get(Type type) { ICbConverterMethods? methods; if (!s_typeToMethods.TryGetValue(type, out methods)) { CbConverter converter = CbConverter.GetConverter(type); methods = (converter as ICbConverterMethods) ?? CreateWrapper(type, converter); s_typeToMethods.Add(type, methods); } return methods; } } /// /// Base class for converter factories /// public abstract class CbConverterFactory { /// /// Create a converter for the given type /// /// The type to create a converter for /// The converter instance, or null if this factory does not support the given type public abstract CbConverter? CreateConverter(Type type); } /// /// Utility functions for creating converters /// public partial class CbConverter { /// /// Object used for locking access to shared objects /// static readonly object s_lockObject = new object(); /// /// Cache of property to converter /// static readonly Dictionary s_propertyToConverter = []; /// /// Cache of type to converter /// public static Dictionary TypeToConverter { get; } = new Dictionary() { [typeof(bool)] = new CbPrimitiveConverter(x => x.AsBool(), (w, v) => w.WriteBoolValue(v), (w, n, v) => w.WriteBool(n, v)), [typeof(int)] = new CbPrimitiveConverter(x => x.AsInt32(), (w, v) => w.WriteIntegerValue(v), (w, n, v) => w.WriteInteger(n, v)), [typeof(uint)] = new CbPrimitiveConverter(x => x.AsUInt32(), (w, v) => w.WriteIntegerValue(v), (w, n, v) => w.WriteInteger(n, v)), [typeof(long)] = new CbPrimitiveConverter(x => x.AsInt64(), (w, v) => w.WriteIntegerValue(v), (w, n, v) => w.WriteInteger(n, v)), [typeof(ulong)] = new CbPrimitiveConverter(x => x.AsUInt64(), (w, v) => w.WriteIntegerValue(v), (w, n, v) => w.WriteInteger(n, v)), [typeof(double)] = new CbPrimitiveConverter(x => x.AsDouble(), (w, v) => w.WriteDoubleValue(v), (w, n, v) => w.WriteDouble(n, v)), [typeof(Utf8String)] = new CbPrimitiveConverter(x => x.AsUtf8String(), (w, v) => w.WriteUtf8StringValue(v), (w, n, v) => w.WriteUtf8String(n, v)), [typeof(IoHash)] = new CbPrimitiveConverter(x => x.AsHash(), (w, v) => w.WriteHashValue(v), (w, n, v) => w.WriteHash(n, v)), [typeof(CbObjectAttachment)] = new CbPrimitiveConverter(x => x.AsObjectAttachment(), (w, v) => w.WriteObjectAttachmentValue(v.Hash), (w, n, v) => w.WriteObjectAttachment(n, v.Hash)), [typeof(CbBinaryAttachment)] = new CbPrimitiveConverter(x => x.AsBinaryAttachment(), (w, v) => w.WriteBinaryAttachmentValue(v.Hash), (w, n, v) => w.WriteBinaryAttachment(n, v.Hash)), [typeof(DateTime)] = new CbPrimitiveConverter(x => x.AsDateTime(), (w, v) => w.WriteDateTimeValue(v), (w, n, v) => w.WriteDateTime(n, v)), [typeof(string)] = new CbPrimitiveConverter(x => x.AsString(), (w, v) => w.WriteStringValue(v), (w, n, v) => w.WriteString(n, v)), [typeof(ReadOnlyMemory)] = new CbPrimitiveConverter>(x => x.AsBinary(), (w, v) => w.WriteBinaryValue(v), (w, n, v) => w.WriteBinary(n, v)), [typeof(byte[])] = new CbPrimitiveConverter(x => x.AsBinaryArray(), (w, v) => w.WriteBinaryArrayValue(v), (w, n, v) => w.WriteBinaryArray(n, v)), [typeof(CbField)] = new CbFieldConverter(), [typeof(CbObject)] = new CbObjectConverter() }; /// /// List of converter factories. Must be /// public static List ConverterFactories { get; } = [ new CbClassConverterFactory(), new CbStringConverterFactory(), new CbEnumConverterFactory(), new CbListConverterFactory(), new CbArrayConverterFactory(), new CbDictionaryConverterFactory(), new CbNullableConverterFactory(), ]; /// /// Class used to cache values returned by CreateConverterInfo without having to do a dictionary lookup. /// /// The type to be converted class CbConverterCache { /// /// The converter instance /// public static CbConverter Instance { get; } = CreateConverter(); /// /// Create a typed converter /// /// static CbConverter CreateConverter() { CbConverter converter = GetConverter(typeof(T)); return (converter as CbConverter) ?? new CbConverterWrapper(converter); } /// /// Wrapper class to convert an untyped converter into a typed one /// class CbConverterWrapper(CbConverter inner) : CbConverter { /// public override T Read(CbField field) => (T)inner.ReadObject(field)!; /// public override void Write(CbWriter writer, T value) => inner.WriteObject(writer, value); /// public override void WriteNamed(CbWriter writer, CbFieldName name, T value) => inner.WriteNamedObject(writer, name, value); } } /// /// Gets the converter for a particular property /// /// /// public static CbConverter GetConverter(PropertyInfo property) { CbConverterAttribute? converterAttribute = property.GetCustomAttribute(); if (converterAttribute != null) { Type converterType = converterAttribute.ConverterType; lock (s_lockObject) { CbConverter? converter; if (!s_propertyToConverter.TryGetValue(property, out converter)) { converter = (CbConverter?)Activator.CreateInstance(converterType)!; s_propertyToConverter.Add(property, converter); } return converter; } } return GetConverter(property.PropertyType); } /// /// Gets the converter for a particular type /// /// /// public static CbConverter GetConverter(Type type) { CbConverter? converter; lock (s_lockObject) { if (!TypeToConverter.TryGetValue(type, out converter)) { CbConverterAttribute? converterAttribute = type.GetCustomAttribute(); if (converterAttribute != null) { Type converterType = converterAttribute.ConverterType; if (type.IsGenericType && converterType.IsGenericTypeDefinition) { converterType = converterType.MakeGenericType(type.GetGenericArguments()); } converter = (CbConverter?)Activator.CreateInstance(converterType)!; } else { for (int idx = ConverterFactories.Count - 1; idx >= 0 && converter == null; idx--) { converter = ConverterFactories[idx].CreateConverter(type); } if (converter == null) { throw new CbException($"Unable to create converter for {type.Name}"); } } TypeToConverter.Add(type, converter!); } } return converter; } /// /// Gets the converter for a given type /// /// Type to retrieve the converter for /// public static CbConverter GetConverter() { try { return CbConverterCache.Instance; } catch (TypeInitializationException ex) when (ex.InnerException != null) { throw ex.InnerException; } } } }