using LiteDB; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using Tango.BL.ActionLogs; using Tango.BL.ValueObjects; using Tango.Core.ExtensionMethods; namespace Tango.BL { /// /// Represents an auto generated observable entity transport object mirror. /// /// The type of to. /// /// /// /// public abstract class ObservableEntityDTO : IObservableEntityDTO, IEquatable, IActionLogComparable where T : IObservableEntity where DTO : ObservableEntityDTO { /// /// Gets or sets the ID of the entity. /// public int ID { get; set; } /// /// Gets or sets the unique identifier of the entity. /// [BsonId] public String Guid { get; set; } /// /// Gets or sets the last updated date of the entity. /// public DateTime LastUpdated { get; set; } /// /// Creates an instance of type for the specified entity of type . /// /// The observable. /// public static DTO FromObservable(T observable) { return FromObservableInternal(observable); } /// /// Creates an instance of type for the specified entity of type . /// /// The observable. /// public static DTOResult FromObservable(T observable) where DTOResult : DTO { return FromObservableInternal(observable); } internal static DTOResult FromObservableInternal(T observable) where DTOResult : DTO { if (observable == null) return null; var dto = Activator.CreateInstance(); foreach (var prop in typeof(DTOResult).GetProperties()) { var observableProp = typeof(T).GetProperty(prop.Name); if (observableProp != null) { if (prop.PropertyType.IsValueTypeOrString() || prop.PropertyType == typeof(byte[])) { prop.SetValue(dto, observableProp.GetValue(observable)); } else if (!prop.PropertyType.IsGenericType) { prop.SetValue(dto, prop.PropertyType.GetMethod(nameof(FromObservableInternal), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy).MakeGenericMethod(prop.PropertyType).Invoke(null, new object[] { observableProp.GetValue(observable) })); } else { IList collection = Activator.CreateInstance(prop.PropertyType) as IList; IList source = observableProp.GetValue(observable) as IList; foreach (var item in source) { collection.Add(prop.PropertyType.GenericTypeArguments[0].GetMethod(nameof(FromObservableInternal), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy).MakeGenericMethod(prop.PropertyType.GenericTypeArguments[0]).Invoke(null, new object[] { item })); } prop.SetValue(dto, collection); } } else if (prop.GetCustomAttribute() != null) { var att = prop.GetCustomAttribute(); try { prop.SetValue(dto, observable.GetPropertyValueByPath(att.MapsTo)); } catch { Debug.WriteLine($"Error mapping '{typeof(DTOResult).Name}.{prop.PropertyType}' to '{typeof(T)}.{att.MapsTo}'."); } } } dto.OnFromObservableCompleted(observable); return dto; } /// /// Creates an entity of type from this DTO. /// /// public T ToObservable() { T observable = Activator.CreateInstance(); MapToObservable(observable); return observable; } /// /// Maps this instance to an instance of . /// /// The observable. public virtual void MapToObservable(T observable) { foreach (var prop in GetType().GetProperties()) { var observableProp = typeof(T).GetProperty(prop.Name); if (observableProp != null) { if (prop.PropertyType.IsValueTypeOrString() || prop.PropertyType == typeof(byte[])) { observableProp.SetValue(observable, prop.GetValue(this)); } else if (!prop.PropertyType.IsGenericType) { var propInstance = prop.GetValue(this); if (propInstance != null) { var observableInstance = observableProp.GetValue(observable); if (observableInstance == null) { observableInstance = Activator.CreateInstance(observableProp.PropertyType); observableProp.SetValue(observable, observableInstance); } var method = prop.PropertyType.GetRuntimeMethod(nameof(MapToObservable), new Type[] { observableProp.PropertyType }); method.Invoke(propInstance, new object[] { observableInstance }); } } else { IList collection = prop.GetValue(this) as IList; IList observableCollection = observableProp.GetValue(observable) as IList; if (collection != null) { foreach (var item in collection) { bool found = false; foreach (var o in observableCollection) { var dtoItem = item as IObservableEntityDTO; var observableItem = o as IObservableEntity; if (dtoItem.Guid.Equals(observableItem.Guid)) { var method = dtoItem.GetType().GetRuntimeMethod(nameof(MapToObservable), new Type[] { observableItem.GetType() }); method.Invoke(dtoItem, new object[] { observableItem }); found = true; break; } } if (!found) { var observableItem = Activator.CreateInstance(observableProp.PropertyType.GenericTypeArguments[0]); var method = item.GetType().GetRuntimeMethod(nameof(MapToObservable), new Type[] { observableItem.GetType() }); method.Invoke(item, new object[] { observableItem }); observableCollection.Add(observableItem); } } } } } } } /// /// Returns true if this instance is equal in terms of values (not references) to the specified entity. /// /// The observable. /// public bool EqualsToObservable(T observable) { if (observable == null) return false; foreach (var prop in typeof(DTO).GetProperties()) { var observableProp = typeof(T).GetProperty(prop.Name); if (observableProp != null) { if (prop.PropertyType.IsValueTypeOrString() || prop.PropertyType == typeof(byte[])) { var observableValue = observableProp.GetValue(observable); var dtoValue = prop.GetValue(this); if (dtoValue == null && observableValue != null) return false; if (dtoValue != null && observableValue == null) return false; if (dtoValue != null && observableValue != null) { if (!dtoValue.Equals(observableValue)) { return false; } } } else if (!prop.PropertyType.IsGenericType) { var propInstance = prop.GetValue(this); var observableInstance = observableProp.GetValue(observable); if (propInstance == null && observableInstance != null) return false; if (propInstance != null) { var method = prop.PropertyType.GetRuntimeMethod(nameof(EqualsToObservable), new Type[] { observableInstance.GetType() }); if (!((bool)method.Invoke(propInstance, new object[] { observableInstance }))) { return false; } } } else { IList source = observableProp.GetValue(observable) as IList; IList collection = prop.GetValue(this) as IList; if (collection == null && source != null) return false; if (source == null && collection != null) return false; if (source != null && collection != null) { if (source.Count != collection.Count) return false; for (int i = 0; i < source.Count; i++) { var item = collection[i]; var itemSource = source[i]; var method = item.GetType().GetRuntimeMethod(nameof(EqualsToObservable), new Type[] { itemSource.GetType() }); if (item == null && itemSource != null) return false; if (!((bool)method.Invoke(item, new object[] { itemSource }))) { return false; } } } } } } return true; } /// /// Returns true if this instance is equal in terms of values (not references) to the specified entity. /// /// The observable. /// public bool Equals(T observable) { return EqualsToObservable(observable); } /// /// Returns true if the specified property should be ignored during ActionLog comparison. /// /// Name of the property. /// bool IActionLogComparable.ShouldActionLogIgnore(string propName) { return propName == nameof(LastUpdated) || OnShouldActionLogIgnore(propName); } /// /// override to specified properties to be ignored when doing ActionLog comparison. /// /// Name of the property. /// protected virtual bool OnShouldActionLogIgnore(string propName) { return false; } /// /// Returns an optional custom name for this instance in the comparison tree. /// /// string IActionLogComparable.GetActionLogName() { return OnGetActionLogName(); } /// /// override to specified a custom name for this instance in the ActionLog comparison tree. /// /// protected virtual String OnGetActionLogName() { return this.GetType().Name; } /// /// Called when the static method completes. /// /// The observable. protected virtual void OnFromObservableCompleted(T observable) { //Just for override } } }