239 lines
6.6 KiB
C#
239 lines
6.6 KiB
C#
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<Triplet[], Element> Decoder;
|
|
public Func<Element, Triplet[]> Encoder;
|
|
}
|
|
|
|
private class FieldData
|
|
{
|
|
public PropertyInfo FieldInfo { get; set; }
|
|
public string Predicate { get; set; }
|
|
}
|
|
|
|
private Dictionary<string, TypeInfo> 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<string, FieldData> 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<Triplet> 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<Triplet> 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<Triplet> 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<Triplet> 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<T>(IEnumerable<Triplet> 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;
|
|
}
|
|
}
|