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
}
}