// 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;
}
}
}
}