using System.ComponentModel; using System.Data.SqlTypes; using System.Diagnostics; using System.Reflection; namespace Salmon.Core.Cliff; public class Translator { public const string TYPE_PREDICATE = "is"; private class TypeInfo { public Func Decoder; public Func Encoder; } private class FieldData { public PropertyInfo FieldInfo { get; set; } public string Predicate { get; set; } } private Dictionary Informations = new (); public void DiscoverAllTypes() { Informations.Clear(); var elemType = typeof(Element); CreateTypeInfo(elemType); foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) foreach (var t in a.GetTypes()) if (t.IsClass && t.IsSubclassOf(elemType)) CreateTypeInfo(t); } public string GetTypename(Type t) { string typename = t.Name; var typeAttr = t.GetCustomAttribute(typeof(SetType), false) as SetType; if (typeAttr is not null) typename = typeAttr.Name; return typename; } public static bool IsNullable(Type type) { return Nullable.GetUnderlyingType(type) != null; } private TypeInfo CreateTypeInfo(Type t) { var ret = new TypeInfo(); Dictionary Fields = new(); string typename = GetTypename(t); foreach (var field in t.GetProperties()) { var persistenceAttributes = field.GetCustomAttributes(typeof(PersistentField), true); if (persistenceAttributes.Length == 0) continue; var persistence = (PersistentField)persistenceAttributes[0]; string name = persistence.Name ?? field.Name; Fields.Add(name, new FieldData { FieldInfo = field, Predicate = name }); } var constructor = t.GetConstructor(new[] { typeof(string) }); if (constructor == null) throw new Exception($"Cannot find contructor(string) for type {t.FullName}"); ret.Encoder = (Element o) => { Debug.Assert(o is not null); Debug.Assert(t.IsInstanceOfType(o)); List triplets = new(); triplets.Add(new Triplet { key = o.UniqueId, predicate = TYPE_PREDICATE, value = typename }); foreach (var fd in Fields) { var val = fd.Value.FieldInfo.GetValue(o); /*if (val is null) continue;*/ triplets.Add(new Triplet { key = o.UniqueId, predicate = fd.Value.Predicate, value = val }); } foreach (var kv in o.SupplementaryProperties) triplets.Add(new Triplet { key = o.UniqueId, predicate = kv.Key, value = kv.Value }); return triplets.ToArray(); }; ret.Decoder = (Triplet[] triplets) => { if (triplets.Length == 0) throw new Exception($"Cannot decode without any triplets"); var ret = constructor.Invoke(new[] { triplets[0].key }) as Element; if(ret is null) throw new Exception($"Calling contructor(string) for type {t.FullName} return null"); foreach (var t in triplets) { string fname = t.predicate; if(Fields.ContainsKey(fname)) { var fieldinfo = Fields[fname].FieldInfo; var objval = t.value; if (objval is string && fieldinfo.PropertyType == typeof(DateTime?)) objval = DateTime.Parse((string)objval); if(objval is not null && !objval.GetType().IsAssignableTo(fieldinfo.PropertyType)) { objval = Convert.ChangeType(objval, Nullable.GetUnderlyingType(fieldinfo.PropertyType)); } fieldinfo.SetValue(ret, objval); } else ret.SupplementaryProperties.Add(fname, t.value); } return ret; }; if(!Informations.TryAdd(typename, ret)) Informations[typename] = ret; return ret; } TypeInfo? GetInfo(IEnumerable triplets) { string? type = null; foreach (var triplet in triplets) if (triplet.predicate == TYPE_PREDICATE) { type = triplet.value as string; break; } if (type == null) return null; if (Informations.Count == 0) DiscoverAllTypes(); if (Informations.TryGetValue(type, out var ret)) return ret; return null; } TypeInfo? GetInfo(Type info) { string typename = GetTypename(info); if (Informations.Count == 0) DiscoverAllTypes(); if (Informations.TryGetValue(typename, out var typeInfo)) return typeInfo; return null; } public IEnumerable Encode(Element instance) { var typeinfo = GetInfo(instance.GetType()); if (typeinfo is null) yield break; foreach(var t in typeinfo.Encoder(instance)) yield return t; } public Element Decode(IEnumerable triples, bool greedy = true) { var typeinfo = GetInfo(triples); if (typeinfo is null) { if(greedy == false) throw new Exception($"Cannot load type information for triplet list"); typeinfo = GetInfo(typeof(Element)); if (typeinfo is null) throw new Exception($"GetInfo(typeof(Element)) return null."); } var decoded = typeinfo.Decoder(triples.ToArray()); if (decoded is null) throw new Exception($"Cannot deserialize triplets set as {typeinfo}."); return decoded; } public T Decode(IEnumerable triples) where T : Element { var decoded = Decode(triples); var typed = decoded as T; if (typed is null) throw new Exception($"Cannot convert decoded type {decoded.GetType()} as {typeof(T)}."); return typed; } }