using System; using System.Collections.Concurrent; using System.Linq.Expressions; using Robust.Shared.Serialization.Manager.Definition; using Robust.Shared.Serialization.Manager.Result; using Robust.Shared.Serialization.Markdown; using Robust.Shared.Serialization.Markdown.Mapping; using Robust.Shared.Serialization.Markdown.Sequence; using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Serialization.TypeSerializers.Interfaces; using Robust.Shared.Utility; namespace Robust.Shared.Serialization.Manager { public partial class SerializationManager { private delegate DeserializationResult ReadDelegate( Type type, DataNode node, ISerializationContext? context = null, bool skipHook = false); private readonly ConcurrentDictionary<(Type value, Type node), ReadDelegate> _readers = new(); private ReadDelegate GetOrCreateReader(Type value, DataNode node) { if (node.Tag?.StartsWith("!type:") ?? false) { var typeString = node.Tag.Substring(6); value = ResolveConcreteType(value, typeString); } return _readers.GetOrAdd((value, node.GetType()), static (tuple, vfArgument) => { var (value, nodeType) = tuple; var (node, instance) = vfArgument; var nullable = value.IsNullable(); value = value.EnsureNotNullableType(); var instanceConst = Expression.Constant(instance); var typeParam = Expression.Parameter(typeof(Type), "type"); var nodeParam = Expression.Parameter(typeof(DataNode), "node"); var contextParam = Expression.Parameter(typeof(ISerializationContext), "context"); var skipHookParam = Expression.Parameter(typeof(bool), "skipHook"); MethodCallExpression call; if (value.IsArray) { var elementType = value.GetElementType()!; switch (node) { case ValueDataNode when nullable: call = Expression.Call( instanceConst, nameof(ReadArrayValue), new[] { elementType }, Expression.Convert(nodeParam, typeof(ValueDataNode))); break; case SequenceDataNode seqNode: var isSealed = elementType.IsPrimitive || elementType.IsEnum || elementType == typeof(string) || elementType.IsSealed; if (isSealed && seqNode.Sequence.Count > 0) { var reader = instance.GetOrCreateReader(elementType, seqNode.Sequence[0]); var readerConst = Expression.Constant(reader); call = Expression.Call( instanceConst, nameof(ReadArraySequenceSealed), new[] { elementType }, Expression.Convert(nodeParam, typeof(SequenceDataNode)), readerConst, contextParam, skipHookParam); break; } call = Expression.Call( instanceConst, nameof(ReadArraySequence), new[] { elementType }, Expression.Convert(nodeParam, typeof(SequenceDataNode)), contextParam, skipHookParam); break; default: throw new ArgumentException($"Cannot read array from data node type {nodeType}"); } } else if (value.IsEnum) { call = node switch { ValueDataNode when nullable => Expression.Call( instanceConst, nameof(ReadEnumNullable), new[] { value }, Expression.Convert(nodeParam, typeof(ValueDataNode))), ValueDataNode => Expression.Call( instanceConst, nameof(ReadEnumValue), new[] { value }, Expression.Convert(nodeParam, typeof(ValueDataNode))), SequenceDataNode => Expression.Call( instanceConst, nameof(ReadEnumSequence), new[] { value }, Expression.Convert(nodeParam, typeof(SequenceDataNode))), _ => throw new InvalidNodeTypeException( $"Cannot serialize node as {value}, unsupported node type {node.GetType()}") }; } else if (value.IsAssignableTo(typeof(ISelfSerialize))) { if (node is not ValueDataNode) { throw new InvalidNodeTypeException( $"Cannot read {nameof(ISelfSerialize)} from node type {nodeType}. Expected {nameof(ValueDataNode)}"); } var instantiator = instance.GetOrCreateInstantiator(value) ?? throw new NullReferenceException($"No instantiator could be made for type {value}"); var instantiatorConst = Expression.Constant(instantiator); call = value.IsValueType switch { true when nullable => call = Expression.Call( instanceConst, nameof(ReadSelfSerializeNullableStruct), new[] {value}, Expression.Convert(nodeParam, typeof(ValueDataNode)), instantiatorConst), _ => call = Expression.Call( instanceConst, nameof(ReadSelfSerialize), new[] { value }, Expression.Convert(nodeParam, typeof(ValueDataNode)), instantiatorConst) }; } else if (instance.TryGetTypeReader(value, nodeType, out var reader)) { var readerType = typeof(ITypeReader<,>).MakeGenericType(value, nodeType); var readerConst = Expression.Constant(reader, readerType); call = node switch { ValueDataNode when nullable && value.IsValueType => Expression.Call( instanceConst, nameof(ReadWithTypeReaderNullableStruct), new[] { value }, Expression.Convert(nodeParam, typeof(ValueDataNode)), readerConst, contextParam, skipHookParam), ValueDataNode when nullable && !value.IsValueType => Expression.Call( instanceConst, nameof(ReadWithTypeReaderNullable), new[] { value }, Expression.Convert(nodeParam, typeof(ValueDataNode)), readerConst, contextParam, skipHookParam), _ => Expression.Call( instanceConst, nameof(ReadWithTypeReader), new[] { value, nodeType }, Expression.Convert(nodeParam, nodeType), readerConst, contextParam, skipHookParam) }; } else if (value.IsInterface || value.IsAbstract) { throw new ArgumentException($"Unable to create an instance of an interface or abstract type. Type: {value}"); } else { var definition = instance.GetDefinition(value); var definitionConst = Expression.Constant(definition, typeof(DataDefinition)); var instantiator = instance.GetOrCreateInstantiator(value); var instantiatorConst = Expression.Constant(instantiator); var populateConst = Expression.Constant(value.IsAssignableTo(typeof(IPopulateDefaultValues))); var hooksConst = Expression.Constant(value.IsAssignableTo(typeof(ISerializationHooks))); call = node switch { ValueDataNode when nullable => Expression.Call( instanceConst, nameof(ReadGenericNullable), new[] { value }, Expression.Convert(nodeParam, typeof(ValueDataNode)), instantiatorConst, definitionConst, populateConst, hooksConst, contextParam, skipHookParam), ValueDataNode => Expression.Call( instanceConst, nameof(ReadGenericValue), new[] { value }, Expression.Convert(nodeParam, typeof(ValueDataNode)), instantiatorConst, definitionConst, populateConst, hooksConst, contextParam, skipHookParam), MappingDataNode => Expression.Call( instanceConst, nameof(ReadGenericMapping), new[] { value }, Expression.Convert(nodeParam, typeof(MappingDataNode)), instantiatorConst, definitionConst, populateConst, hooksConst, contextParam, skipHookParam), SequenceDataNode => throw new ArgumentException($"No mapping node provided for type {value} at line: {node.Start.Line}"), _ => throw new ArgumentException($"Unknown node type {nodeType} provided. Expected mapping node at line: {node.Start.Line}") }; } return Expression.Lambda( call, typeParam, nodeParam, contextParam, skipHookParam).Compile(); }, (node, this)); } private DeserializationResult ReadArrayValue(ValueDataNode value) { if (value.Value == "null") { return new DeserializedValue(null); } throw new InvalidNodeTypeException("Cannot read an array from a value data node that is not null."); } private DeserializationResult ReadArraySequence( SequenceDataNode node, ISerializationContext? context = null, bool skipHook = false) { var type = typeof(T); var array = new T[node.Sequence.Count]; var results = new DeserializationResult[node.Sequence.Count]; for (var i = 0; i < node.Sequence.Count; i++) { var subNode = node.Sequence[i]; var result = Read(type, subNode, context, skipHook); results[i] = result; array[i] = (T) result.RawValue!; } return new DeserializedArray(array, results); } private DeserializationResult ReadArraySequenceSealed( SequenceDataNode node, ReadDelegate elementReader, ISerializationContext? context = null, bool skipHook = false) { var type = typeof(T); var array = new T[node.Sequence.Count]; var results = new DeserializationResult[node.Sequence.Count]; for (var i = 0; i < node.Sequence.Count; i++) { var subNode = node.Sequence[i]; var result = elementReader(type, subNode, context, skipHook); results[i] = result; array[i] = (T) result.RawValue!; } return new DeserializedArray(array, results); } private DeserializationResult ReadEnumNullable(ValueDataNode node) where TEnum : struct { if (node.Value == "null") { return new DeserializedValue(null); } var value = Enum.Parse(node.Value, true); return new DeserializedValue(value); } private DeserializationResult ReadEnumValue(ValueDataNode node) where TEnum : struct { var value = Enum.Parse(node.Value, true); return new DeserializedValue(value); } private DeserializationResult ReadEnumSequence(SequenceDataNode node) where TEnum : struct { var value = Enum.Parse(string.Join(", ", node.Sequence), true); return new DeserializedValue(value); } private DeserializationResult ReadSelfSerialize( ValueDataNode node, InstantiationDelegate instantiator) where TValue : ISelfSerialize { if (node.Value == "null") { return new DeserializedValue(default); } var value = (TValue) instantiator(); value.Deserialize(node.Value); return new DeserializedValue(value); } private DeserializationResult ReadSelfSerializeNullableStruct( ValueDataNode node, InstantiationDelegate instantiator) where TValue : struct, ISelfSerialize { if (node.Value == "null") { return new DeserializedValue(null); } var value = (TValue) instantiator(); value.Deserialize(node.Value); return new DeserializedValue(value); } private DeserializationResult ReadWithTypeReaderNullable( ValueDataNode node, ITypeReader reader, ISerializationContext? context = null, bool skipHook = false) { if (node.Value == "null") { return new DeserializedValue(default); } return ReadWithTypeReader(node, reader, context, skipHook); } private DeserializationResult ReadWithTypeReaderNullableStruct( ValueDataNode node, ITypeReader reader, ISerializationContext? context = null, bool skipHook = false) where TValue : struct { if (node.Value == "null") { return new DeserializedValue(null); } return ReadWithTypeReader(node, reader, context, skipHook); } private DeserializationResult ReadWithTypeReader( TNode node, ITypeReader reader, ISerializationContext? context = null, bool skipHook = false) where TNode : DataNode { if (context != null && context.TypeReaders.TryGetValue((typeof(TValue), typeof(TNode)), out var readerUnCast)) { reader = (ITypeReader) readerUnCast; } return reader.Read(this, node, DependencyCollection, skipHook, context); } private DeserializationResult ReadGenericNullable( ValueDataNode node, InstantiationDelegate instantiator, DataDefinition? definition, bool populate, bool hooks, ISerializationContext? context = null, bool skipHook = false) { if (node.Value == "null") { return new DeserializedValue(default); } return ReadGenericValue(node, instantiator, definition, populate, hooks, context, skipHook); } private DeserializationResult ReadGenericValue( ValueDataNode node, InstantiationDelegate instantiator, DataDefinition? definition, bool populate, bool hooks, ISerializationContext? context = null, bool skipHook = false) { var type = typeof(TValue); if (context != null && context.TypeReaders.TryGetValue((typeof(TValue), typeof(ValueDataNode)), out var readerUnCast)) { var reader = (ITypeReader) readerUnCast; return reader.Read(this, node, DependencyCollection, skipHook, context); } if (definition == null) { throw new ArgumentException($"No data definition found for type {type} with node type {node.GetType()} when reading"); } var instance = instantiator(); if (populate) { ((IPopulateDefaultValues) instance).PopulateDefaultValues(); } if (node.Value != string.Empty) { throw new ArgumentException($"No mapping node provided for type {type} at line: {node.Start.Line}"); } // If we get an empty ValueDataNode we just use an empty mapping var mapping = new MappingDataNode(); var result = definition.Populate(instance, mapping, this, context, skipHook); if (!skipHook && hooks) { ((ISerializationHooks) result.RawValue!).AfterDeserialization(); } return result; } private DeserializationResult ReadGenericMapping( MappingDataNode node, InstantiationDelegate instantiator, DataDefinition? definition, bool populate, bool hooks, ISerializationContext? context = null, bool skipHook = false) { var type = typeof(TValue); var instance = instantiator(); if (context != null && context.TypeReaders.TryGetValue((type, typeof(MappingDataNode)), out var readerUnCast)) { var reader = (ITypeReader) readerUnCast; return reader.Read(this, node, DependencyCollection, skipHook, context); } if (definition == null) { throw new ArgumentException($"No data definition found for type {type} with node type {node.GetType()} when reading"); } if (populate) { ((IPopulateDefaultValues) instance).PopulateDefaultValues(); } var result = definition.Populate(instance, node, this, context, skipHook); if (!skipHook && hooks) { ((ISerializationHooks) result.RawValue!).AfterDeserialization(); } return result; } public DeserializationResult Read(Type type, DataNode node, ISerializationContext? context = null, bool skipHook = false) { return GetOrCreateReader(type, node)(type, node, context, skipHook); } public object? ReadValue(Type type, DataNode node, ISerializationContext? context = null, bool skipHook = false) { return Read(type, node, context, skipHook).RawValue; } public T? ReadValueCast(Type type, DataNode node, ISerializationContext? context = null, bool skipHook = false) { var value = Read(type, node, context, skipHook); if (value.RawValue == null) { return default; } return (T?) value.RawValue; } public T? ReadValue(DataNode node, ISerializationContext? context = null, bool skipHook = false) { return ReadValueCast(typeof(T), node, context, skipHook); } public DeserializationResult ReadWithTypeSerializer(Type value, Type serializer, DataNode node, ISerializationContext? context = null, bool skipHook = false) { return ReadWithSerializerRaw(value, node, serializer, context, skipHook); } } }