using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using Tango.BL.ValueObjects; namespace Tango.BL.ActionLogs { /// /// Represents the default implementation of . /// public class DefaultActionLogComparer : IActionLogComparer { /// /// Compares the specified object before and after changes and returns the difference tree. /// /// The object before the change. /// The object after the change. /// public Task Compare(IActionLogComparable before, IActionLogComparable after) { return Task.Factory.StartNew(() => { ActionLogDifference diff = new ActionLogDifference(); diff.Name = GetComponentName(before, after); Compare(diff, before, after, null); RemoveNoDifferences(diff); return diff; }); } #region Helpers private void Compare(ActionLogDifference diff, Object before, Object after, HashSet scannedObjects) { if (scannedObjects == null) { scannedObjects = new HashSet(); } if (before == null && after == null) return; if (before != null) { if (scannedObjects.Contains(before)) return; scannedObjects.Add(before); } if (after != null) { if (scannedObjects.Contains(after)) return; scannedObjects.Add(after); } foreach (var prop in GetProperties(before, after).OrderByDescending(x => x.PropertyType.IsValueTypeOrString())) { if (prop.PropertyType == typeof(byte[]) || GetShouldIgnore(prop, before, after)) continue; if (prop.PropertyType.IsValueTypeOrString()) { object beforeValue = null; object afterValue = null; if (before != null) { beforeValue = prop.GetValue(before); } if (after != null) { afterValue = prop.GetValue(after); } if (afterValue == null && beforeValue != null) AddValueDiff(diff, prop.Name, beforeValue, afterValue); if (afterValue != null && beforeValue == null) AddValueDiff(diff, prop.Name, beforeValue, afterValue); if (afterValue != null && beforeValue != null) { if (!afterValue.Equals(beforeValue)) { AddValueDiff(diff, prop.Name, beforeValue, afterValue); } } } else if (!prop.PropertyType.IsGenericType) { object beforePropInstance = null; object afterPropInstance = null; if (before != null) { beforePropInstance = prop.GetValue(before); } if (after != null) { afterPropInstance = prop.GetValue(after); } Compare(AddChildDiff(diff, prop.Name), beforePropInstance, afterPropInstance, scannedObjects); } else { IList beforeCollection = null; IList afterCollection = null; ActionLogDifference listDiff = new ActionLogDifference() { Name = prop.Name }; if (before != null) { beforeCollection = prop.GetValue(before) as IList; } if (after != null) { afterCollection = prop.GetValue(after) as IList; } int listCount = 0; if (beforeCollection != null && afterCollection == null) { for (int i = 0; i < beforeCollection.Count; i++) { listCount++; Compare(AddChildDiff(listDiff, GetActionLogName(beforeCollection[i], prop.Name)), beforeCollection[i], null, scannedObjects); } } else if (beforeCollection == null && afterCollection != null) { for (int i = 0; i < afterCollection.Count; i++) { listCount++; Compare(AddChildDiff(listDiff, GetActionLogName(afterCollection[i], prop.Name)), null, afterCollection[i], scannedObjects); } } if (beforeCollection != null && afterCollection != null) { for (int i = 0; i < Math.Max(beforeCollection.Count, afterCollection.Count); i++) { var beforeItem = i < beforeCollection.Count ? beforeCollection[i] : null; var afterItem = i < afterCollection.Count ? afterCollection[i] : null; listCount++; Compare(AddChildDiff(listDiff, GetActionLogName(beforeItem, prop.Name)), beforeItem, afterItem, scannedObjects); } } if (listCount > 0) { diff.Children.Add(listDiff); } } } } private void AddValueDiff(ActionLogDifference diff, String property, object before, object after) { diff.Children.Add(new ActionLogDifferenceValue() { Name = property, Before = before, After = after }); } private ActionLogDifference AddChildDiff(ActionLogDifference diff, String property) { ActionLogDifference childDiff = new ActionLogDifference(); childDiff.Name = property; diff.Children.Add(childDiff); return childDiff; } private void RemoveNoDifferences(ActionLogDifference diff) { foreach (var child in diff.Children.ToList()) { if (!child.HasDifference) { diff.Children.Remove(child); } else { RemoveNoDifferences(child); } } } private String GetComponentName(Object before, Object after) { var afterCast = after as IActionLogComparable; var beforeCast = before as IActionLogComparable; var name = afterCast != null ? afterCast.GetActionLogName() : beforeCast.GetActionLogName(); return name; } private String GetActionLogName(Object obj, String defaultName) { var objCast = obj as IActionLogComparable; if (objCast != null) { return objCast.GetActionLogName(); } else { return defaultName; } } private PropertyInfo GetProperty(String name, Object before, Object after) { return after != null ? after.GetType().GetProperty(name) : before.GetType().GetProperty(name); } private List GetProperties(BindingFlags flags, Object before, Object after) { return after != null ? after.GetType().GetProperties(flags).ToList() : before.GetType().GetProperties(flags).ToList(); } private List GetProperties(Object before, Object after) { return after != null ? after.GetType().GetProperties().ToList() : before.GetType().GetProperties().ToList(); } private bool GetShouldIgnore(PropertyInfo prop, Object before, Object after) { if (prop.GetCustomAttribute() != null) { return true; } var beforeCast = before as IActionLogComparable; var afterCast = after as IActionLogComparable; if (beforeCast != null) return beforeCast.ShouldActionLogIgnore(prop.Name); if (afterCast != null) return afterCast.ShouldActionLogIgnore(prop.Name); return false; } #endregion } }