|
@@ -2,10 +2,15 @@
|
|
|
using System.Collections.Generic;
|
|
|
using System.Diagnostics.CodeAnalysis;
|
|
|
using System.IO;
|
|
|
+using System.Linq;
|
|
|
+using System.Reflection;
|
|
|
+using System.Runtime.InteropServices.ComTypes;
|
|
|
using System.Threading;
|
|
|
+using System.Xml.Linq;
|
|
|
using InABox.Clients;
|
|
|
using JetBrains.Annotations;
|
|
|
using Newtonsoft.Json;
|
|
|
+using Newtonsoft.Json.Linq;
|
|
|
|
|
|
namespace InABox.Core
|
|
|
{
|
|
@@ -214,4 +219,369 @@ namespace InABox.Core
|
|
|
|
|
|
#endregion
|
|
|
}
|
|
|
+
|
|
|
+ public static class SerializationUtils
|
|
|
+ {
|
|
|
+ public static void Write(this BinaryWriter writer, Guid guid)
|
|
|
+ {
|
|
|
+ writer.Write(guid.ToByteArray());
|
|
|
+ }
|
|
|
+ public static Guid ReadGuid(this BinaryReader reader)
|
|
|
+ {
|
|
|
+ return new Guid(reader.ReadBytes(16));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Binary serialize a bunch of different types of values. <see cref="WriteBinaryValue(BinaryWriter, Type, object?)"/> and
|
|
|
+ /// <see cref="ReadBinaryValue(BinaryReader, Type)"/> are inverses of each other.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// Handles <see cref="byte"/>[], <see cref="Array"/>s of serialisable values, <see cref="Enum"/>, <see cref="bool"/>, <see cref="string"/>,
|
|
|
+ /// <see cref="Guid"/>, <see cref="byte"/>, <see cref="Int16"/>, <see cref="Int32"/>, <see cref="Int64"/>, <see cref="float"/>, <see cref="double"/>,
|
|
|
+ /// <see cref="DateTime"/>, <see cref="TimeSpan"/>, <see cref="LoggablePropertyAttribute"/>, <see cref="IPackable"/>, <see cref="Nullable{T}"/>
|
|
|
+ /// and <see cref="ISerializeBinary"/>.
|
|
|
+ /// </remarks>
|
|
|
+ /// <param name="writer"></param>
|
|
|
+ /// <param name="type"></param>
|
|
|
+ /// <param name="value"></param>
|
|
|
+ /// <exception cref="Exception">If an object of <paramref name="type"/> is unable to be serialized.</exception>
|
|
|
+ public static void WriteBinaryValue(this BinaryWriter writer, Type type, object? value)
|
|
|
+ {
|
|
|
+ value ??= CoreUtils.GetDefault(type);
|
|
|
+ if (type == typeof(byte[]) && value is byte[] bArray)
|
|
|
+ {
|
|
|
+ writer.Write(bArray.Length);
|
|
|
+ writer.Write(bArray);
|
|
|
+ }
|
|
|
+ else if (type == typeof(byte[]) && value is null)
|
|
|
+ {
|
|
|
+ writer.Write(0);
|
|
|
+ }
|
|
|
+ else if (type.IsArray && value is Array array)
|
|
|
+ {
|
|
|
+ var elementType = type.GetElementType();
|
|
|
+ writer.Write(array.Length);
|
|
|
+ foreach (var val1 in array)
|
|
|
+ {
|
|
|
+ WriteBinaryValue(writer, elementType, val1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (type.IsArray && value is null)
|
|
|
+ {
|
|
|
+ writer.Write(0);
|
|
|
+ }
|
|
|
+ else if (type.IsEnum && value is Enum e)
|
|
|
+ {
|
|
|
+ var underlyingType = type.GetEnumUnderlyingType();
|
|
|
+ WriteBinaryValue(writer, underlyingType, Convert.ChangeType(e, underlyingType));
|
|
|
+ }
|
|
|
+ else if (type == typeof(bool) && value is bool b)
|
|
|
+ {
|
|
|
+ writer.Write(b);
|
|
|
+ }
|
|
|
+ else if (type == typeof(string) && value is string str)
|
|
|
+ {
|
|
|
+ writer.Write(str);
|
|
|
+ }
|
|
|
+ else if (type == typeof(string) && value is null)
|
|
|
+ {
|
|
|
+ writer.Write("");
|
|
|
+ }
|
|
|
+ else if (type == typeof(Guid) && value is Guid guid)
|
|
|
+ {
|
|
|
+ writer.Write(guid);
|
|
|
+ }
|
|
|
+ else if (type == typeof(byte) && value is byte i8)
|
|
|
+ {
|
|
|
+ writer.Write(i8);
|
|
|
+ }
|
|
|
+ else if (type == typeof(Int16) && value is Int16 i16)
|
|
|
+ {
|
|
|
+ writer.Write(i16);
|
|
|
+ }
|
|
|
+ else if (type == typeof(Int32) && value is Int32 i32)
|
|
|
+ {
|
|
|
+ writer.Write(i32);
|
|
|
+ }
|
|
|
+ else if (type == typeof(Int64) && value is Int64 i64)
|
|
|
+ {
|
|
|
+ writer.Write(i64);
|
|
|
+ }
|
|
|
+ else if (type == typeof(float) && value is float f32)
|
|
|
+ {
|
|
|
+ writer.Write(f32);
|
|
|
+ }
|
|
|
+ else if (type == typeof(double) && value is double f64)
|
|
|
+ {
|
|
|
+ writer.Write(f64);
|
|
|
+ }
|
|
|
+ else if (type == typeof(DateTime) && value is DateTime date)
|
|
|
+ {
|
|
|
+ writer.Write(date.Ticks);
|
|
|
+ }
|
|
|
+ else if (type == typeof(TimeSpan) && value is TimeSpan time)
|
|
|
+ {
|
|
|
+ writer.Write(time.Ticks);
|
|
|
+ }
|
|
|
+ else if (type == typeof(LoggablePropertyAttribute))
|
|
|
+ {
|
|
|
+ writer.Write((value as LoggablePropertyAttribute)?.Format ?? "");
|
|
|
+ }
|
|
|
+ else if (typeof(IPackable).IsAssignableFrom(type) && value is IPackable pack)
|
|
|
+ {
|
|
|
+ pack.Pack(writer);
|
|
|
+ }
|
|
|
+ else if (typeof(ISerializeBinary).IsAssignableFrom(type) && value is ISerializeBinary binary)
|
|
|
+ {
|
|
|
+ binary.SerializeBinary(writer);
|
|
|
+ }
|
|
|
+ else if (Nullable.GetUnderlyingType(type) is Type t)
|
|
|
+ {
|
|
|
+ if(value == null)
|
|
|
+ {
|
|
|
+ writer.Write(false);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ writer.Write(true);
|
|
|
+ writer.WriteBinaryValue(t, value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ throw new Exception($"Invalid type; Target DataType is {type} and value DataType is {value?.GetType().ToString() ?? "null"}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Binary deserialize a bunch of different types of values. <see cref="WriteBinaryValue(BinaryWriter, Type, object?)"/> and
|
|
|
+ /// <see cref="ReadBinaryValue(BinaryReader, Type)"/> are inverses of each other.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// Handles <see cref="byte"/>[], <see cref="Array"/>s of serialisable values, <see cref="Enum"/>, <see cref="bool"/>, <see cref="string"/>,
|
|
|
+ /// <see cref="Guid"/>, <see cref="byte"/>, <see cref="Int16"/>, <see cref="Int32"/>, <see cref="Int64"/>, <see cref="float"/>, <see cref="double"/>,
|
|
|
+ /// <see cref="DateTime"/>, <see cref="TimeSpan"/>, <see cref="LoggablePropertyAttribute"/>, <see cref="IPackable"/>, <see cref="Nullable{T}"/>
|
|
|
+ /// and <see cref="ISerializeBinary"/>.
|
|
|
+ /// </remarks>
|
|
|
+ /// <param name="writer"></param>
|
|
|
+ /// <param name="type"></param>
|
|
|
+ /// <param name="value"></param>
|
|
|
+ /// <exception cref="Exception">If an object of <paramref name="type"/> is unable to be deserialized.</exception>
|
|
|
+ public static object? ReadBinaryValue(this BinaryReader reader, Type type)
|
|
|
+ {
|
|
|
+ if (type == typeof(byte[]))
|
|
|
+ {
|
|
|
+ var length = reader.ReadInt32();
|
|
|
+ return reader.ReadBytes(length);
|
|
|
+ }
|
|
|
+ else if (type.IsArray)
|
|
|
+ {
|
|
|
+ var length = reader.ReadInt32();
|
|
|
+ var elementType = type.GetElementType();
|
|
|
+
|
|
|
+ var array = Array.CreateInstance(elementType, length);
|
|
|
+ for (int i = 0; i < array.Length; ++i)
|
|
|
+ {
|
|
|
+ array.SetValue(ReadBinaryValue(reader, elementType), i);
|
|
|
+ }
|
|
|
+ return array;
|
|
|
+ }
|
|
|
+ else if (type.IsEnum)
|
|
|
+ {
|
|
|
+ var val = ReadBinaryValue(reader, type.GetEnumUnderlyingType());
|
|
|
+ return Enum.ToObject(type, val);
|
|
|
+ }
|
|
|
+ else if (type == typeof(bool))
|
|
|
+ {
|
|
|
+ return reader.ReadBoolean();
|
|
|
+ }
|
|
|
+ else if (type == typeof(string))
|
|
|
+ {
|
|
|
+ return reader.ReadString();
|
|
|
+ }
|
|
|
+ else if (type == typeof(Guid))
|
|
|
+ {
|
|
|
+ return reader.ReadGuid();
|
|
|
+ }
|
|
|
+ else if (type == typeof(byte))
|
|
|
+ {
|
|
|
+ return reader.ReadByte();
|
|
|
+ }
|
|
|
+ else if (type == typeof(Int16))
|
|
|
+ {
|
|
|
+ return reader.ReadInt16();
|
|
|
+ }
|
|
|
+ else if (type == typeof(Int32))
|
|
|
+ {
|
|
|
+ return reader.ReadInt32();
|
|
|
+ }
|
|
|
+ else if (type == typeof(Int64))
|
|
|
+ {
|
|
|
+ return reader.ReadInt64();
|
|
|
+ }
|
|
|
+ else if (type == typeof(float))
|
|
|
+ {
|
|
|
+ return reader.ReadSingle();
|
|
|
+ }
|
|
|
+ else if (type == typeof(double))
|
|
|
+ {
|
|
|
+ return reader.ReadDouble();
|
|
|
+ }
|
|
|
+ else if (type == typeof(DateTime))
|
|
|
+ {
|
|
|
+ return new DateTime(reader.ReadInt64());
|
|
|
+ }
|
|
|
+ else if (type == typeof(TimeSpan))
|
|
|
+ {
|
|
|
+ return new TimeSpan(reader.ReadInt64());
|
|
|
+ }
|
|
|
+ else if (type == typeof(LoggablePropertyAttribute))
|
|
|
+ {
|
|
|
+ String format = reader.ReadString();
|
|
|
+ return String.IsNullOrWhiteSpace(format)
|
|
|
+ ? null
|
|
|
+ : new LoggablePropertyAttribute() { Format = format };
|
|
|
+ }
|
|
|
+ else if (typeof(IPackable).IsAssignableFrom(type))
|
|
|
+ {
|
|
|
+ var packable = (Activator.CreateInstance(type) as IPackable)!;
|
|
|
+ packable.Unpack(reader);
|
|
|
+ return packable;
|
|
|
+ }
|
|
|
+ else if (typeof(ISerializeBinary).IsAssignableFrom(type))
|
|
|
+ {
|
|
|
+ var obj = (Activator.CreateInstance(type) as ISerializeBinary)!;
|
|
|
+ obj.DeserializeBinary(reader);
|
|
|
+ return obj;
|
|
|
+ }
|
|
|
+ else if (Nullable.GetUnderlyingType(type) is Type t)
|
|
|
+ {
|
|
|
+ var isNull = reader.ReadBoolean();
|
|
|
+ if (isNull)
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return reader.ReadBinaryValue(t);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ throw new Exception($"Invalid type; Target DataType is {type}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static IEnumerable<IProperty> SerializableProperties(Type type) =>
|
|
|
+ DatabaseSchema.Properties(type)
|
|
|
+ .Where(x => !(x is StandardProperty st) || st.Property.GetCustomAttribute<DoNotSerialize>() == null);
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// An implementation of binary serialising a <typeparamref name="TObject"/>; this is the inverse of <see cref="ReadObject{TObject}(BinaryReader)"/>.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// Also serialises the names of properties along with the values.
|
|
|
+ /// </remarks>
|
|
|
+ /// <typeparam name="TObject"></typeparam>
|
|
|
+ /// <param name="writer"></param>
|
|
|
+ /// <param name="entity"></param>
|
|
|
+ public static void WriteObject<TObject>(this BinaryWriter writer, TObject entity)
|
|
|
+ where TObject : BaseObject, new()
|
|
|
+ {
|
|
|
+ var properties = SerializableProperties(typeof(TObject)).ToList();
|
|
|
+
|
|
|
+ writer.Write(properties.Count);
|
|
|
+ foreach (var property in properties)
|
|
|
+ {
|
|
|
+ writer.Write(property.Name);
|
|
|
+ writer.WriteBinaryValue(property.PropertyType, property.Getter()(entity));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// The inverse of <see cref="WriteObject{TObject}(BinaryWriter, TObject)"/>.
|
|
|
+ /// </summary>
|
|
|
+ /// <typeparam name="TObject"></typeparam>
|
|
|
+ /// <param name="reader"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public static TObject ReadObject<TObject>(this BinaryReader reader)
|
|
|
+ where TObject : BaseObject, new()
|
|
|
+ {
|
|
|
+ var obj = new TObject();
|
|
|
+
|
|
|
+ var nProps = reader.ReadInt32();
|
|
|
+ for(int i = 0; i < nProps; ++i)
|
|
|
+ {
|
|
|
+ var propName = reader.ReadString();
|
|
|
+ var property = DatabaseSchema.Property(typeof(TObject), propName);
|
|
|
+ property?.Setter()(obj, reader.ReadBinaryValue(property.PropertyType));
|
|
|
+ }
|
|
|
+ return obj;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// An implementation of binary serialising multiple <typeparamref name="TObject"/>s;
|
|
|
+ /// this is the inverse of <see cref="ReadObjects{TObject}(BinaryReader)"/>.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// Also serialises the names of properties along with the values.
|
|
|
+ /// </remarks>
|
|
|
+ /// <typeparam name="TObject"></typeparam>
|
|
|
+ /// <param name="writer"></param>
|
|
|
+ /// <param name="entity"></param>
|
|
|
+ public static void WriteObjects<TObject>(this BinaryWriter writer, ICollection<TObject> objects)
|
|
|
+ where TObject : BaseObject, new()
|
|
|
+ {
|
|
|
+ var properties = SerializableProperties(typeof(TObject)).ToList();
|
|
|
+
|
|
|
+ writer.Write(objects.Count);
|
|
|
+ writer.Write(properties.Count);
|
|
|
+ foreach (var property in properties)
|
|
|
+ {
|
|
|
+ writer.Write(property.Name);
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (var obj in objects)
|
|
|
+ {
|
|
|
+ foreach (var property in properties)
|
|
|
+ {
|
|
|
+ writer.WriteBinaryValue(property.PropertyType, property.Getter()(obj));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// The inverse of <see cref="WriteObjects{TObject}(BinaryWriter, IList{TObject})"/>.
|
|
|
+ /// </summary>
|
|
|
+ /// <typeparam name="TObject"></typeparam>
|
|
|
+ /// <param name="reader"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public static List<TObject> ReadObjects<TObject>(this BinaryReader reader)
|
|
|
+ where TObject : BaseObject, new()
|
|
|
+ {
|
|
|
+ var objs = new List<TObject>();
|
|
|
+ var properties = new List<IProperty>();
|
|
|
+
|
|
|
+ var nObjs = reader.ReadInt32();
|
|
|
+ var nProps = reader.ReadInt32();
|
|
|
+ for(int i = 0; i < nProps; ++i)
|
|
|
+ {
|
|
|
+ var property = reader.ReadString();
|
|
|
+ properties.Add(DatabaseSchema.Property(typeof(TObject), property));
|
|
|
+ }
|
|
|
+
|
|
|
+ for(int i = 0; i < nObjs; ++i)
|
|
|
+ {
|
|
|
+ var obj = new TObject();
|
|
|
+
|
|
|
+ foreach(var property in properties)
|
|
|
+ {
|
|
|
+ property?.Setter()(obj, reader.ReadBinaryValue(property.PropertyType));
|
|
|
+ }
|
|
|
+
|
|
|
+ objs.Add(obj);
|
|
|
+ }
|
|
|
+ return objs;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|