using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Runtime.CompilerServices; using System.Text; using static x0.Jester.DataType; namespace x0.Jester { public class Serializer { public const byte Version = 1; private readonly TypeInspector _inspector; public Serializer() : this(new SerializerSettings()) { } public Serializer(SerializerSettings settings) => _inspector = new TypeInspector(settings); public byte[] Serialize(T source) { var type = source?.GetType() ?? typeof(T); var desc = GetTypeDescriptor(type); using (var stream = new MemoryStream(1024)) { using (var writer = new BinaryWriter(stream, Encoding.UTF8, false)) { var ctx = new SerializationContext(writer, this); writer.Write(Version); writer.Write(desc.TypeId == 0 ? DataType.Object.Id : desc.TypeId); WriteObject(writer, source, type, desc, ctx); return stream.ToArray(); } } } public void Serialize(T source, Stream stream) { var type = source?.GetType() ?? typeof(T); var desc = GetTypeDescriptor(type); using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) { var ctx = new SerializationContext(writer, this); writer.Write(Version); writer.Write(desc.TypeId == 0 ? DataType.Object.Id : desc.TypeId); WriteObject(writer, source, type, desc, ctx); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private TypeDescriptor GetTypeDescriptor(Type type) => _inspector.InspectType(type); internal void WriteObject(BinaryWriter writer, object source, Type type, SerializationContext ctx) => WriteObject(writer, source, type, GetTypeDescriptor(type), ctx); private void WriteObject(BinaryWriter writer, object source, Type type, TypeDescriptor descriptor, SerializationContext ctx) { if (source == null) { writer.Write(NoValue); return; } writer.Write(HasValue); if (!ctx.LoopingSet.Add(source)) { throw new JesterWriteException("Data loop detected"); } if (descriptor.Converter != null) { WriteViaConverter(descriptor.Converter, writer, source, type, ctx); } else { writer.Write((int) 0); var pos = writer.BaseStream.Position; // to be compatible with dict serialization we write key-value types writer.Write(_inspector.InspectType(typeof(string)).TypeId); writer.Write((byte) 0); WriteObjectFields(writer, source, descriptor, ctx); WriteLength(writer, pos); } if (!ctx.LoopingSet.Remove(source)) { throw new JesterWriteException("Serialization inconsistency: this is a bug"); } } private void WriteObjectFields(BinaryWriter writer, object source, TypeDescriptor descriptor, SerializationContext ctx) { foreach (var member in descriptor.Members) { var value = member.Get(source); if (!member.WriteDefaultValue && value == member.DefaultValue) { continue; } var type = value?.GetType() ?? member.MemberType; var desc = GetTypeDescriptor(type); writer.Write(desc.TypeId == 0 ? DataType.Object.Id : desc.TypeId); writer.Write(Encoding.UTF8.GetBytes(member.Name)); writer.Write((byte) 0); WriteObject(writer, value, type, desc, ctx); } } private void WriteViaConverter(JesterConverter conv, BinaryWriter writer, object source, Type type, SerializationContext ctx) { if (conv.IsFixedSize) { conv.Write(writer, source, type, ctx); } else { writer.Write((int) 0); var pos = writer.BaseStream.Position; conv.Write(writer, source, type, ctx); WriteLength(writer, pos); } } internal void WriteDictionary(BinaryWriter writer, IDictionary source, Type dictType, SerializationContext ctx) { writer.Write((byte) 0); writer.Write((byte) 0); if (source != null) { foreach (DictionaryEntry entry in source) { WriteDictionaryEntry(writer, entry.Key, entry.Value, ctx); } } } internal void WriteDictionary(BinaryWriter writer, IEnumerable> source, Type dictType, SerializationContext ctx) { writer.Write(_inspector.InspectType(typeof(TK)).TypeId); writer.Write(_inspector.InspectType(typeof(TV)).TypeId); if (source != null) { foreach (var entry in source) { WriteDictionaryEntry(writer, entry.Key, entry.Value, ctx); } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteDictionaryEntry(BinaryWriter writer, object key, T value, SerializationContext ctx) { var name = (key as IConvertible)?.ToString(CultureInfo.InvariantCulture) ?? key.ToString(); var type = value?.GetType() ?? typeof(T); var desc = GetTypeDescriptor(type); writer.Write(desc.TypeId == 0 ? DataType.Object.Id : desc.TypeId); writer.Write(Encoding.UTF8.GetBytes(name)); writer.Write((byte) 0); WriteObject(writer, value, type, desc, ctx); } internal void WriteCollection(BinaryWriter writer, IEnumerable source, Type collectionType, SerializationContext ctx) { writer.Write((byte) 0); foreach (var item in source) { WriteCollectionEntry(writer, item, ctx); } } internal void WriteCollection(BinaryWriter writer, IEnumerable source, Type collectionType, SerializationContext ctx) { var itemDesc = GetTypeDescriptor(typeof(T)); var fixedSize = itemDesc.Converter is IPrimitiveConverter c && c.IsFixedSize; writer.Write(fixedSize ? itemDesc.TypeId : (byte) 0); // it is a primitive type which has a converter defined if (fixedSize) { var conv = itemDesc.Converter; var type = conv.Type; foreach (var item in source) { conv.Write(writer, item, type, ctx); } } else { foreach (var item in source) { WriteCollectionEntry(writer, item, ctx); } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteCollectionEntry(BinaryWriter writer, T value, SerializationContext ctx) { var type = value?.GetType() ?? typeof(T); var desc = GetTypeDescriptor(type); writer.Write(desc.TypeId == 0 ? DataType.Object.Id : desc.TypeId); WriteObject(writer, value, type, desc, ctx); } private void WriteLength(BinaryWriter writer, long atPos) { var currentPos = writer.BaseStream.Position; if (currentPos != atPos) { writer.BaseStream.Seek(atPos - sizeof(int), SeekOrigin.Begin); writer.Write((int) (currentPos - atPos)); writer.BaseStream.Seek(currentPos, SeekOrigin.Begin); } } } public class SerializationContext { internal BinaryWriter Writer { get; } internal Serializer Serializer { get; } internal HashSet LoopingSet { get; } = new HashSet(); internal SerializationContext(BinaryWriter writer, Serializer serializer) { Writer = writer; Serializer = serializer; } public void Write(T value) => Serializer.WriteObject(Writer, value, value?.GetType() ?? typeof(T), this); public void Write(object value, Type type) => Serializer.WriteObject(Writer, value, type, this); } }