private static void DiffValue(Diff3Node diff3, ref NodeDescription baseNodeDesc, ref NodeDescription asset1NodeDesc, ref NodeDescription asset2NodeDesc) { var node = diff3.Asset1Node ?? diff3.Asset2Node ?? diff3.BaseNode; var dataVisitMember = node as DataVisitMember; if (dataVisitMember != null) { var specificAssetAttribute = dataVisitMember.MemberDescriptor.GetCustomAttributes <DiffUseSpecificAssetAttribute>(true).FirstOrDefault(); if (specificAssetAttribute != null) { if (specificAssetAttribute is DiffUseAsset1Attribute) { diff3.ChangeType = Diff3ChangeType.MergeFromAsset1; } else if (specificAssetAttribute is DiffUseAsset2Attribute) { diff3.ChangeType = Diff3ChangeType.MergeFromAsset2; } else { throw new InvalidOperationException(); } return; } } var baseAsset1Equals = Equals(baseNodeDesc.Instance, asset1NodeDesc.Instance); var baseAsset2Equals = Equals(baseNodeDesc.Instance, asset2NodeDesc.Instance); var asset1And2Equals = Equals(asset1NodeDesc.Instance, asset2NodeDesc.Instance); diff3.ChangeType = baseAsset1Equals && baseAsset2Equals ? Diff3ChangeType.None : baseAsset2Equals ? Diff3ChangeType.MergeFromAsset1 : baseAsset1Equals ? Diff3ChangeType.MergeFromAsset2 : asset1And2Equals ? Diff3ChangeType.MergeFromAsset1And2 : Diff3ChangeType.Conflict; }
private static void ApplyOverrideOnValue(Diff3Node diff3, bool isClassType = false) { var memberBase = (diff3.BaseNode) as DataVisitMember; var memberAsset1 = (diff3.Asset1Node) as DataVisitMember; var memberAsset2 = (diff3.Asset2Node) as DataVisitMember; var baseOverride = memberBase?.Parent?.Instance?.GetOverride(memberBase.MemberDescriptor) ?? OverrideType.Base; var member1Override = memberAsset1?.Parent?.Instance?.GetOverride(memberAsset1.MemberDescriptor) ?? OverrideType.Base; var member2Override = memberAsset2?.Parent?.Instance?.GetOverride(memberAsset2.MemberDescriptor) ?? OverrideType.Base; if (member2Override.IsSealed()) { diff3.ChangeType = Diff3ChangeType.MergeFromAsset2; // Force asset1 override to be base|sealed diff3.FinalOverride = OverrideType.Base | OverrideType.Sealed; } else if (member1Override.IsBase()) { diff3.ChangeType = Diff3ChangeType.MergeFromAsset2; diff3.FinalOverride = member1Override; } else { diff3.ChangeType = Diff3ChangeType.MergeFromAsset1; diff3.FinalOverride = member1Override; } // If base changed from Sealed to non-sealed, and asset1 was base|sealed, change it back to plain base. if (baseOverride.IsSealed() && !member2Override.IsSealed() && member1Override == (OverrideType.Base | OverrideType.Sealed)) { diff3.FinalOverride = OverrideType.Base; } }
private void DiffMembers(Diff3Node diff3, DataVisitNode baseNode, DataVisitNode asset1Node, DataVisitNode asset2Node) { var baseMembers = baseNode != null ? baseNode.Members : null; var asset1Members = asset1Node != null ? asset1Node.Members : null; var asset2Members = asset2Node != null ? asset2Node.Members : null; int memberCount = 0; if (baseMembers != null) { memberCount = baseMembers.Count; } else if (asset1Members != null) { memberCount = asset1Members.Count; } else if (asset2Members != null) { memberCount = asset2Members.Count; } for (int i = 0; i < memberCount; i++) { AddMember(diff3, DiffNode(baseMembers == null ? null : baseMembers[i], asset1Members == null ? null : asset1Members[i], asset2Members == null ? null : asset2Members[i])); } }
/// <summary> /// Diff a collection of Guid (supposed to be unique) /// </summary> /// <param name="diff3"></param> /// <param name="baseNode"></param> /// <param name="asset1Node"></param> /// <param name="asset2Node"></param> private void DiffCollectionByGuids(Diff3Node diff3, DataVisitNode baseNode, DataVisitNode asset1Node, DataVisitNode asset2Node) { DiffCollectionByIdsGeneric(diff3, baseNode, asset1Node, asset2Node, o => (Guid)o, (a1, a2, a3) => new Diff3Node(a1, a2, a3) { InstanceType = typeof(Guid), ChangeType = Diff3ChangeType.MergeFromAsset1 }); }
private Diff3Node DiffNode(DataVisitNode baseNode, DataVisitNode asset1Node, DataVisitNode asset2Node) { var diff3 = new Diff3Node(baseNode, asset1Node, asset2Node); var baseNodeDesc = GetNodeDescription(baseNode); var asset1NodeDesc = GetNodeDescription(asset1Node); var asset2NodeDesc = GetNodeDescription(asset2Node); if (asset1NodeDesc.Type == asset2NodeDesc.Type) { if (baseNodeDesc.Type == asset1NodeDesc.Type) { // If all types are the same, perform a normal diff. return(DiffNodeWithUniformType(baseNode, asset1Node, asset2Node)); } else { // If base has a different type, but asset1 and asset2 are equal, use them. Otherwise there is a conflict with base. var temp = DiffNodeWithUniformType(asset1Node, asset1Node, asset2Node); diff3.ChangeType = temp.ChangeType == Diff3ChangeType.None ? Diff3ChangeType.MergeFromAsset1And2 : Diff3ChangeType.Conflict; diff3.InstanceType = asset1NodeDesc.Type; } } else if (baseNodeDesc.Type == asset1NodeDesc.Type) { // If base and asset 1 are equal, use asset 2. var temp = DiffNodeWithUniformType(baseNode, asset1Node, asset1Node); diff3.ChangeType = temp.ChangeType == Diff3ChangeType.None ? Diff3ChangeType.MergeFromAsset2 : Diff3ChangeType.Conflict; diff3.InstanceType = asset2NodeDesc.Type; } else if (baseNodeDesc.Type == asset2NodeDesc.Type) { // If base and asset 2 are equal, use asset 1. var temp = DiffNodeWithUniformType(baseNode, asset2Node, asset2Node); diff3.ChangeType = temp.ChangeType == Diff3ChangeType.None ? Diff3ChangeType.MergeFromAsset1 : Diff3ChangeType.Conflict; diff3.InstanceType = asset1NodeDesc.Type; } else { // If one asset is unspecified, use the other. // If all types are different, there is a type conflict. if (asset1Node == null) { diff3.ChangeType = Diff3ChangeType.MergeFromAsset2; diff3.InstanceType = asset2NodeDesc.Type; } else if (asset2Node == null) { diff3.ChangeType = Diff3ChangeType.MergeFromAsset1; diff3.InstanceType = asset1NodeDesc.Type; } else { diff3.ChangeType = Diff3ChangeType.ConflictType; } } return(diff3); }
/// <summary> /// Adds an item (array, list or dictionary item) to this instance. /// </summary> /// <param name="thisObject">The this object.</param> /// <param name="item">The item.</param> /// <exception cref="System.ArgumentNullException">item</exception> private static void AddItem(Diff3Node thisObject, Diff3Node item, bool hasChildrenChanged = false) { if (thisObject.Items == null) { thisObject.Items = new List <Diff3Node>(); } // Add at the end of the list AddItemByPosition(thisObject, item, thisObject.Items.Count, hasChildrenChanged); }
private static void DiffValue(Diff3Node diff3, ref NodeDescription baseNodeDesc, ref NodeDescription asset1NodeDesc, ref NodeDescription asset2NodeDesc) { var baseAsset1Equals = Equals(baseNodeDesc.Instance, asset1NodeDesc.Instance); var baseAsset2Equals = Equals(baseNodeDesc.Instance, asset2NodeDesc.Instance); var asset1And2Equals = Equals(asset1NodeDesc.Instance, asset2NodeDesc.Instance); diff3.ChangeType = baseAsset1Equals && baseAsset2Equals ? Diff3ChangeType.None : baseAsset2Equals ? Diff3ChangeType.MergeFromAsset1 : baseAsset1Equals ? Diff3ChangeType.MergeFromAsset2 : asset1And2Equals ? Diff3ChangeType.MergeFromAsset1And2 : Diff3ChangeType.Conflict; }
/// <summary> /// The merge policy is expecting <c>asset2</c> to be the new base of <c>asset1</c>, and <c>base</c> the current base of <c>asset1</c>. /// In case of conflicts: /// <ul> /// <li>If there is a type conflict, leave as-is</li> /// <li>If there is a <see cref="Diff3ChangeType.Conflict"/>: /// <ul> /// <li>If it is on a member, and both <c>asset1</c> and <c>asset2</c> are not null and changes were done /// in <c>asset2</c> but not in <c>asset1</c>, select <c>asset2</c> else select <c>asset1</c></li> /// <li>If it is on a list item, we can't resolve it and leave the conflict as-is</li> /// <li>If it is on a array item, we select <c>asset2</c></li> /// </ul> /// </li> /// </ul> /// </summary> /// <param name="diff3Node">The diff3 node.</param> /// <returns>.</returns> public static Diff3ChangeType MergePolicyAsset2AsNewBaseOfAsset1(Diff3Node diff3Node) { var hasConflicts = diff3Node.ChangeType > Diff3ChangeType.Conflict; if (hasConflicts) { return(diff3Node.ChangeType); } switch (diff3Node.ChangeType) { case Diff3ChangeType.Conflict: case Diff3ChangeType.MergeFromAsset2: var dataNode = diff3Node.Asset2Node ?? diff3Node.Asset1Node ?? diff3Node.BaseNode; if (dataNode is DataVisitMember) { if (diff3Node.Asset1Node != null && diff3Node.Asset2Node != null && diff3Node.ChangeType == Diff3ChangeType.MergeFromAsset2) { return(Diff3ChangeType.MergeFromAsset2); } return(Diff3ChangeType.MergeFromAsset1); } if (dataNode is DataVisitListItem) { // If we have a conflict in a list, we can't really resolve it here, so we are breaking out of the loop if (diff3Node.ChangeType == Diff3ChangeType.Conflict) { return(Diff3ChangeType.Conflict); } return(Diff3ChangeType.MergeFromAsset2); } if (dataNode is DataVisitDictionaryItem) { return(Diff3ChangeType.MergeFromAsset2); } if (dataNode is DataVisitArrayItem) { if (diff3Node.Asset1Node != null && ((DataVisitArrayItem)diff3Node.Asset1Node).Index == ((DataVisitArrayItem)dataNode).Index) { return(Diff3ChangeType.MergeFromAsset2); } } break; } return(diff3Node.ChangeType); }
/// <summary> /// Computes the diff3 between <see cref="BaseAsset" />, <see cref="Asset1" /> and <see cref="Asset2" />. /// </summary> /// <param name="forceRecompute">if set to <c>true</c> force to recompute the diff.</param> /// <returns>The result of the diff. This result is cached so next call will return it directly.</returns> public Diff3Node Compute(bool forceRecompute = false) { if (computed != null && !forceRecompute) { return(computed); } var baseNodes = DataVisitNodeBuilder.Run(TypeDescriptorFactory.Default, baseAsset); var asset1Nodes = DataVisitNodeBuilder.Run(TypeDescriptorFactory.Default, asset1); var asset2Nodes = DataVisitNodeBuilder.Run(TypeDescriptorFactory.Default, asset2); computed = DiffNode(baseNodes, asset1Nodes, asset2Nodes); return(computed); }
/// <summary> /// Adds a member to this instance. /// </summary> /// <param name="thisObject">The this object.</param> /// <param name="member">The member.</param> /// <exception cref="System.ArgumentNullException">member</exception> private static void AddMember(Diff3Node thisObject, Diff3Node member) { if (member == null) { throw new ArgumentNullException("member"); } if (thisObject.Members == null) { thisObject.Members = new List <Diff3Node>(); } member.Parent = thisObject; if (member.ChangeType != Diff3ChangeType.None) { thisObject.ChangeType = Diff3ChangeType.Children; } thisObject.Members.Add(member); }
/// <summary> /// Adds an item (array, list or dictionary item) to this instance. /// </summary> /// <param name="thisObject">The this object.</param> /// <param name="item">The item.</param> /// <exception cref="System.ArgumentNullException">item</exception> private static void AddItem(Diff3Node thisObject, Diff3Node item) { if (item == null) { throw new ArgumentNullException("item"); } if (thisObject.Items == null) { thisObject.Items = new List <Diff3Node>(); } item.Parent = thisObject; if (item.ChangeType != Diff3ChangeType.None) { thisObject.ChangeType = Diff3ChangeType.Children; } thisObject.Items.Add(item); }
private Diff3Node DiffNodeWithUniformType(DataVisitNode baseNode, DataVisitNode asset1Node, DataVisitNode asset2Node) { var baseNodeDesc = GetNodeDescription(baseNode); var asset1NodeDesc = GetNodeDescription(asset1Node); var asset2NodeDesc = GetNodeDescription(asset2Node); var node = baseNode ?? asset1Node ?? asset2Node; var type = baseNodeDesc.Type ?? asset1NodeDesc.Type ?? asset2NodeDesc.Type; var diff3 = new Diff3Node(baseNode, asset1Node, asset2Node) { InstanceType = type }; if (type == null) { // All nodes are null. This should only happen as part of a temporary diff in DiffNode() diff3.ChangeType = Diff3ChangeType.None; } else if (IsComparableType(node.HasMembers, type)) { DiffValue(diff3, ref baseNodeDesc, ref asset1NodeDesc, ref asset2NodeDesc); } else { DiffMembers(diff3, baseNode, asset1Node, asset2Node); if (DictionaryDescriptor.IsDictionary(type)) { DiffDictionary(diff3, baseNode, asset1Node, asset2Node); } else if (CollectionDescriptor.IsCollection(type)) { DiffCollection(diff3, baseNode, asset1Node, asset2Node); } else if (type.IsArray) { DiffArray(diff3, baseNode, asset1Node, asset2Node); } } return(diff3); }
private void DiffArray(Diff3Node diff3, DataVisitNode baseNode, DataVisitNode asset1Node, DataVisitNode asset2Node) { var baseItems = baseNode != null ? baseNode.Items : null; var asset1Items = asset1Node != null ? asset1Node.Items : null; var asset2Items = asset2Node != null ? asset2Node.Items : null; int itemCount = -1; if (baseItems != null) { itemCount = baseItems.Count; } if (asset1Items != null) { var newLength = asset1Items.Count; if (itemCount >= 0 && itemCount != newLength) { diff3.ChangeType = Diff3ChangeType.ConflictArraySize; return; } itemCount = newLength; } if (asset2Items != null) { var newLength = asset2Items.Count; if (itemCount >= 0 && itemCount != newLength) { diff3.ChangeType = Diff3ChangeType.ConflictArraySize; return; } itemCount = newLength; } for (int i = 0; i < itemCount; i++) { AddItem(diff3, DiffNode(baseItems == null ? null : baseItems[i], asset1Items == null ? null : asset1Items[i], asset2Items == null ? null : asset2Items[i])); } // TODO: Add diff by ids on array }
/// <summary> /// Adds an item (array, list or dictionary item) to this instance. /// </summary> /// <param name="thisObject">The this object.</param> /// <param name="item">The item.</param> /// <exception cref="System.ArgumentNullException">item</exception> private static void AddItem(Diff3Node thisObject, Diff3Node item, bool hasChildrenChanged = false) { if (item == null) { throw new ArgumentNullException("item"); } if (thisObject.Items == null) { thisObject.Items = new List <Diff3Node>(); } item.Parent = thisObject; if (item.ChangeType != Diff3ChangeType.None || hasChildrenChanged) { thisObject.ChangeType = Diff3ChangeType.Children; } item.Index = thisObject.Items.Count; thisObject.Items.Add(item); }
/// <summary> /// Computes the diff3 between <see cref="BaseAsset" />, <see cref="Asset1" /> and <see cref="Asset2" />. /// </summary> /// <param name="forceRecompute">if set to <c>true</c> force to recompute the diff.</param> /// <returns>The result of the diff. This result is cached so next call will return it directly.</returns> public Diff3Node Compute(bool forceRecompute = false) { if (computed != null && !forceRecompute) { return(computed); } // If asset implement IDiffResolver, run callback if (baseAsset is IDiffResolver) { ((IDiffResolver)baseAsset).BeforeDiff(baseAsset, asset1, asset2); } var baseNodes = DataVisitNodeBuilder.Run(TypeDescriptorFactory.Default, baseAsset); var asset1Nodes = DataVisitNodeBuilder.Run(TypeDescriptorFactory.Default, asset1); var asset2Nodes = DataVisitNodeBuilder.Run(TypeDescriptorFactory.Default, asset2); computed = DiffNode(baseNodes, asset1Nodes, asset2Nodes); return(computed); }
private void DiffValue(Diff3Node diff3, ref NodeDescription baseNodeDesc, ref NodeDescription asset1NodeDesc, ref NodeDescription asset2NodeDesc) { var node = diff3.Asset1Node ?? diff3.Asset2Node ?? diff3.BaseNode; var dataVisitMember = node as DataVisitMember; if (dataVisitMember != null) { var diffMember = dataVisitMember.MemberDescriptor.GetCustomAttributes <DiffMemberAttribute>(true).FirstOrDefault(); if (diffMember != null) { if (diffMember.PreferredChange.HasValue) { diff3.ChangeType = diffMember.PreferredChange.Value; } diff3.Weight = diffMember.Weight; } } var instanceType = asset1NodeDesc.Instance?.GetType() ?? asset2NodeDesc.Instance?.GetType(); object baseInstance = baseNodeDesc.Instance; object asset1Instance = asset1NodeDesc.Instance; object asset2Instance = asset2NodeDesc.Instance; // If this is an identifiable type (but we are for example not visiting its member), compare only the Ids instead if (UseOverrideMode && instanceType != null && IdentifiableHelper.IsIdentifiable(instanceType)) { baseInstance = IdentifiableHelper.GetId(baseInstance); asset1Instance = IdentifiableHelper.GetId(asset1Instance); asset2Instance = IdentifiableHelper.GetId(asset2Instance); } var baseAsset1Equals = Equals(baseInstance, asset1Instance); var baseAsset2Equals = Equals(baseInstance, asset2Instance); var asset1And2Equals = Equals(asset1Instance, asset2Instance); diff3.ChangeType = baseAsset1Equals && baseAsset2Equals ? Diff3ChangeType.None : baseAsset2Equals ? Diff3ChangeType.MergeFromAsset1 : baseAsset1Equals ? Diff3ChangeType.MergeFromAsset2 : asset1And2Equals ? Diff3ChangeType.MergeFromAsset1And2 : Diff3ChangeType.Conflict; }
private Diff3Node DiffNodeWithUniformType(DataVisitNode baseNode, DataVisitNode asset1Node, DataVisitNode asset2Node) { var baseNodeDesc = GetNodeDescription(baseNode); var asset1NodeDesc = GetNodeDescription(asset1Node); var asset2NodeDesc = GetNodeDescription(asset2Node); var node = baseNode ?? asset1Node ?? asset2Node; var type = baseNodeDesc.Type ?? asset1NodeDesc.Type ?? asset2NodeDesc.Type; var diff3 = new Diff3Node(baseNode, asset1Node, asset2Node) { InstanceType = type }; if (IsComparableType(node.HasMembers, type)) { DiffValue(diff3, ref baseNodeDesc, ref asset1NodeDesc, ref asset2NodeDesc); } else { DiffMembers(diff3, baseNode, asset1Node, asset2Node); if (DictionaryDescriptor.IsDictionary(type)) { DiffDictionary(diff3, baseNode, asset1Node, asset2Node); } else if (CollectionDescriptor.IsCollection(type)) { DiffCollection(diff3, baseNode, asset1Node, asset2Node); } else if (type.IsArray) { DiffArray(diff3, baseNode, asset1Node, asset2Node); } } return(diff3); }
private static void AddItemByPosition(Diff3Node thisObject, Diff3Node item, int position, bool hasChildrenChanged = false) { if (item == null) { throw new ArgumentNullException("item"); } if (thisObject.Items == null) { thisObject.Items = new List <Diff3Node>(); } item.Parent = thisObject; if (item.ChangeType != Diff3ChangeType.None || hasChildrenChanged) { thisObject.ChangeType = Diff3ChangeType.Children; } if (position >= thisObject.Items.Count) { int count = (position - thisObject.Items.Count + 1); for (int i = 0; i < count; i++) { thisObject.Items.Add(null); } } if (thisObject.Items[position] == null) { item.Index = position; thisObject.Items[position] = item; } else { item.Index = position + 1; thisObject.Items.Insert(item.Index, item); } }
private static void DiffValue(Diff3Node diff3, ref NodeDescription baseNodeDesc, ref NodeDescription asset1NodeDesc, ref NodeDescription asset2NodeDesc) { var node = diff3.Asset1Node ?? diff3.Asset2Node ?? diff3.BaseNode; var dataVisitMember = node as DataVisitMember; var diffMember = dataVisitMember?.MemberDescriptor.GetCustomAttributes <DiffMemberAttribute>(true).FirstOrDefault(); if (diffMember != null) { if (diffMember.PreferredChange.HasValue) { diff3.ChangeType = diffMember.PreferredChange.Value; } diff3.Weight = diffMember.Weight; } var baseAsset1Equals = Equals(baseNodeDesc.Instance, asset1NodeDesc.Instance); var baseAsset2Equals = Equals(baseNodeDesc.Instance, asset2NodeDesc.Instance); var asset1And2Equals = Equals(asset1NodeDesc.Instance, asset2NodeDesc.Instance); diff3.ChangeType = baseAsset1Equals && baseAsset2Equals ? Diff3ChangeType.None : baseAsset2Equals ? Diff3ChangeType.MergeFromAsset1 : baseAsset1Equals ? Diff3ChangeType.MergeFromAsset2 : asset1And2Equals ? Diff3ChangeType.MergeFromAsset1And2 : Diff3ChangeType.Conflict; }
public void Reset() { computed = null; }
private Diff3Node DiffNode(DataVisitNode baseNode, DataVisitNode asset1Node, DataVisitNode asset2Node) { var diff3 = new Diff3Node(baseNode, asset1Node, asset2Node); var baseNodeDesc = GetNodeDescription(baseNode); var asset1NodeDesc = GetNodeDescription(asset1Node); var asset2NodeDesc = GetNodeDescription(asset2Node); bool hasMembers = false; Type type = null; Type nodeType = null; if (baseNodeDesc.Type != null) { type = baseNodeDesc.Type; hasMembers = baseNode.HasMembers; nodeType = baseNode.GetType(); } if (asset1NodeDesc.Type != null) { if (type == null) { type = asset1NodeDesc.Type; hasMembers = asset1Node.HasMembers; nodeType = asset1Node.GetType(); } else { if (nodeType != asset1Node.GetType()) { diff3.ChangeType = Diff3ChangeType.InvalidNodeType; return(diff3); } if (type != asset1NodeDesc.Type) { diff3.ChangeType = Diff3ChangeType.ConflictType; return(diff3); } } } if (asset2NodeDesc.Type != null) { if (type == null) { type = asset2NodeDesc.Type; hasMembers = asset2Node.HasMembers; } else { if (nodeType != asset2Node.GetType()) { diff3.ChangeType = Diff3ChangeType.InvalidNodeType; return(diff3); } if (type != asset2NodeDesc.Type) { diff3.ChangeType = Diff3ChangeType.ConflictType; return(diff3); } } } if (type == null) { return(diff3); } diff3.InstanceType = type; // A comparable type doesn't have any members, is not a collection or dictionary or array. bool isComparableType = !hasMembers && !CollectionDescriptor.IsCollection(type) && !DictionaryDescriptor.IsDictionary(type) && !type.IsArray; if (isComparableType) { DiffValue(diff3, ref baseNodeDesc, ref asset1NodeDesc, ref asset2NodeDesc); return(diff3); } // Diff members DiffMembers(diff3, baseNode, asset1Node, asset2Node); if (DictionaryDescriptor.IsDictionary(type)) { DiffDictionary(diff3, baseNode, asset1Node, asset2Node); } else if (CollectionDescriptor.IsCollection(type)) { DiffCollection(diff3, baseNode, asset1Node, asset2Node); } else if (type.IsArray) { DiffArray(diff3, baseNode, asset1Node, asset2Node); } return(diff3); }
private void DiffDictionary(Diff3Node diff3, DataVisitNode baseNode, DataVisitNode asset1Node, DataVisitNode asset2Node) { diff3.Type = Diff3NodeType.Dictionary; var baseItems = baseNode != null ? baseNode.Items : null; var asset1Items = asset1Node != null ? asset1Node.Items : null; var asset2Items = asset2Node != null ? asset2Node.Items : null; // Build dictionary: key => base, v1, v2 var keyNodes = new Dictionary <object, Diff3DictionaryItem>(); Diff3DictionaryItem diff3Item; if (baseItems != null) { foreach (var dataVisitNode in baseItems.OfType <DataVisitDictionaryItem>()) { keyNodes.Add(dataVisitNode.Key, new Diff3DictionaryItem() { Base = dataVisitNode }); } } if (asset1Items != null) { foreach (var dataVisitNode in asset1Items.OfType <DataVisitDictionaryItem>()) { keyNodes.TryGetValue(dataVisitNode.Key, out diff3Item); diff3Item.Asset1 = dataVisitNode; keyNodes[dataVisitNode.Key] = diff3Item; } } if (asset2Items != null) { foreach (var dataVisitNode in asset2Items.OfType <DataVisitDictionaryItem>()) { keyNodes.TryGetValue(dataVisitNode.Key, out diff3Item); diff3Item.Asset2 = dataVisitNode; keyNodes[dataVisitNode.Key] = diff3Item; } } // Perform merge on dictionary foreach (var keyNode in keyNodes) { var valueNode = keyNode.Value; Diff3Node diffValue; // base v1 v2 action // ---- -- -- ------ // a b c Diff(a,b,c) // null b c Diff(null, b, c) if (valueNode.Asset1 != null && valueNode.Asset2 != null) { diffValue = DiffNode(valueNode.Base, valueNode.Asset1, valueNode.Asset2); } else if (valueNode.Asset1 == null) { // a null c MergeFrom1 (unchanged) // null null c MergeFrom2 // a null null MergeFrom1 (unchanged) diffValue = new Diff3Node(valueNode.Base, null, valueNode.Asset2) { ChangeType = valueNode.Base == null ? Diff3ChangeType.MergeFromAsset2 : Diff3ChangeType.MergeFromAsset1 }; } else { // a a null MergeFrom2 (removed) // a b null Conflict // null b null MergeFrom1 (unchanged) var changeType = Diff3ChangeType.MergeFromAsset1; if (valueNode.Base != null) { var diffNode = DiffNode(valueNode.Base, valueNode.Asset1, valueNode.Base); changeType = diffNode.FindDifferences().Any() ? Diff3ChangeType.Conflict : Diff3ChangeType.MergeFromAsset2; } diffValue = new Diff3Node(valueNode.Base, valueNode.Asset1, null) { ChangeType = changeType }; } AddItem(diff3, diffValue); } }
private void DiffCollection(Diff3Node diff3, DataVisitNode baseNode, DataVisitNode asset1Node, DataVisitNode asset2Node) { diff3.Type = Diff3NodeType.Collection; var baseItems = baseNode != null ? baseNode.Items ?? EmptyNodes : EmptyNodes; var asset1Items = asset1Node != null ? asset1Node.Items ?? EmptyNodes : EmptyNodes; var asset2Items = asset2Node != null ? asset2Node.Items ?? EmptyNodes : EmptyNodes; var itemEqualityComparer = equalityComparer; var node = diff3.Asset1Node ?? diff3.Asset2Node ?? diff3.BaseNode; IEnumerable <Diff3Change> changes; bool recurseDiff = false; // Find an item in any of the list var firstItem = baseItems.FirstOrDefault() ?? asset1Items.FirstOrDefault() ?? asset2Items.FirstOrDefault(); // If we have a DiffUseAsset1Attribute, list of Asset1Node becomes authoritative. var dataVisitMember = node as DataVisitMember; var specificAssetAttribute = dataVisitMember != null?dataVisitMember.MemberDescriptor.GetCustomAttributes <DiffUseSpecificAssetAttribute>(true).FirstOrDefault() : null; if (specificAssetAttribute != null) { var isFromAsset2 = specificAssetAttribute is DiffUseAsset2Attribute; var diffChange = isFromAsset2 ? new Diff3Change { ChangeType = SharpDiff.Diff3ChangeType.MergeFrom2, From2 = new Span(0, asset2Items.Count - 1) } : new Diff3Change { ChangeType = SharpDiff.Diff3ChangeType.MergeFrom1, From1 = new Span(0, asset1Items.Count - 1) }; changes = new[] { diffChange }; // TODO: Try to merge back data of matching nodes } else if (firstItem != null && typeof(IDiffKey).IsAssignableFrom(firstItem.InstanceType)) { // If item implement IDataDiffKey, we will use that as equality key changes = Diff3.Compare( baseItems.Select(x => ((IDiffKey)x.Instance).GetDiffKey()).ToList(), asset1Items.Select(x => ((IDiffKey)x.Instance).GetDiffKey()).ToList(), asset2Items.Select(x => ((IDiffKey)x.Instance).GetDiffKey()).ToList()); recurseDiff = true; } else { // Otherwise, do a full node comparison itemEqualityComparer.Reset(); changes = Diff3.Compare(baseItems, asset1Items, asset2Items, itemEqualityComparer); } foreach (var change in changes) { switch (change.ChangeType) { case SharpDiff.Diff3ChangeType.Equal: for (int i = 0; i < change.Base.Length; i++) { var diff3Node = recurseDiff ? DiffNode(baseItems[change.Base.From + i], asset1Items[change.From1.From + i], asset2Items[change.From2.From + i]) : new Diff3Node(baseItems[change.Base.From + i], asset1Items[change.From1.From + i], asset2Items[change.From2.From + i]) { ChangeType = Diff3ChangeType.None }; AddItem(diff3, diff3Node, change.From1.From != 0); } break; case SharpDiff.Diff3ChangeType.MergeFrom1: for (int i = 0; i < change.From1.Length; i++) { var diff3Node = new Diff3Node(null, asset1Items[change.From1.From + i], null) { ChangeType = Diff3ChangeType.MergeFromAsset1 }; AddItem(diff3, diff3Node, change.From1.From != 0); } break; case SharpDiff.Diff3ChangeType.MergeFrom2: for (int i = 0; i < change.From2.Length; i++) { var diff3Node = new Diff3Node(null, null, asset2Items[change.From2.From + i]) { ChangeType = Diff3ChangeType.MergeFromAsset2 }; AddItem(diff3, diff3Node, true); } break; case SharpDiff.Diff3ChangeType.MergeFrom1And2: for (int i = 0; i < change.From2.Length; i++) { var diff3Node = recurseDiff ? DiffNode(null, asset1Items[change.From1.From + i], asset2Items[change.From2.From + i]) : new Diff3Node(null, asset1Items[change.From1.From + i], asset2Items[change.From2.From + i]) { ChangeType = Diff3ChangeType.MergeFromAsset1And2 }; AddItem(diff3, diff3Node, change.From1.From != 0); } break; case SharpDiff.Diff3ChangeType.Conflict: int baseIndex = change.Base.IsValid ? change.Base.From : -1; int from1Index = change.From1.IsValid ? change.From1.From : -1; int from2Index = change.From2.IsValid ? change.From2.From : -1; // If there are changes only from 1 or 2 or base.Length == list1.Length == list2.Length, then try to make a diff per item // else output the conflict as a full conflict bool tryResolveConflict = false; if (baseIndex >= 0) { if (from1Index >= 0 && from2Index >= 0) { if ((change.Base.Length == change.From1.Length && change.Base.Length == change.From2.Length) || (change.From1.Length == change.From2.Length)) { tryResolveConflict = true; } } else if (from1Index >= 0) { tryResolveConflict = change.Base.Length == change.From1.Length; } else if (from2Index >= 0) { tryResolveConflict = change.Base.Length == change.From2.Length; } else { tryResolveConflict = true; } } // Iterate on items while ((baseIndex >= 0 && baseItems.Count > 0) || (from1Index >= 0 && asset1Items.Count > 0) || (from2Index >= 0 && asset2Items.Count > 0)) { var baseItem = GetSafeFromList(baseItems, ref baseIndex, ref change.Base); var asset1Item = GetSafeFromList(asset1Items, ref from1Index, ref change.From1); var asset2Item = GetSafeFromList(asset2Items, ref from2Index, ref change.From2); var diff3Node = tryResolveConflict || recurseDiff? DiffNode(baseItem, asset1Item, asset2Item) : new Diff3Node(baseItem, asset1Item, asset2Item) { ChangeType = Diff3ChangeType.Conflict }; AddItem(diff3, diff3Node, true); } break; } } // Any missing item? (we can detect this only at the end) var newItemCount = diff3.Items != null ? diff3.Items.Count : 0; if (asset1Items.Count != newItemCount) { diff3.ChangeType = Diff3ChangeType.Children; } }
private Diff3Node DiffNodeByOverride(DataVisitNode baseNode, DataVisitNode asset1Node, DataVisitNode asset2Node) { var diff3 = new Diff3Node(baseNode, asset1Node, asset2Node); // TODO Add merge when types are non uniform but we are in UseOverrideMode var baseNodeDesc = GetNodeDescription(baseNode); var asset1NodeDesc = GetNodeDescription(asset1Node); var asset2NodeDesc = GetNodeDescription(asset2Node); var memberBase = (diff3.BaseNode) as DataVisitMember; var memberAsset1 = (diff3.Asset1Node) as DataVisitMember; var memberAsset2 = (diff3.Asset2Node) as DataVisitMember; var dataVisitMember = memberBase ?? memberAsset1 ?? memberAsset2; // Currently, only properties/fields can have override information, so we process them separately here if (dataVisitMember != null) { var type = baseNodeDesc.Type ?? asset1NodeDesc.Type ?? asset2NodeDesc.Type; diff3.InstanceType = type; if (IsComparableType(dataVisitMember.HasMembers, type)) { ApplyOverrideOnValue(diff3); } else { var baseOverride = memberBase?.Parent?.Instance?.GetOverride(memberBase.MemberDescriptor) ?? OverrideType.Base; var member1Override = memberAsset1?.Parent?.Instance?.GetOverride(memberAsset1.MemberDescriptor) ?? OverrideType.Base; var member2Override = memberAsset2?.Parent?.Instance?.GetOverride(memberAsset2.MemberDescriptor) ?? OverrideType.Base; // base asset1 asset2 // ---- ------ ------ // Type: TB T1 T2 // Override: whatever base|whatever (new|base)+sealed -> If member on asset2 is sealed, or member on asset1 is base // // if T1 == T2 and TB == T1, merge all of them // If T1 == T2 // merge T1-T2 // if T1 != T2 // replace asset1 by asset2 value, don't perform merge on instance // // In all case, replace override on asset1 by Base|Sealed if (member2Override.IsSealed() || member1Override.IsBase()) { if (asset1NodeDesc.Type == asset2NodeDesc.Type) { diff3 = DiffNodeWithUniformType(baseNodeDesc.Type == asset1NodeDesc.Type ? baseNode : asset2Node, asset1Node, asset2Node); } else { diff3.InstanceType = asset2NodeDesc.Type; diff3.ChangeType = Diff3ChangeType.MergeFromAsset2; } // Force asset1 override to be base|sealed diff3.FinalOverride = OverrideType.Base; if (member2Override.IsSealed()) { diff3.FinalOverride |= OverrideType.Sealed; } } else { if (asset1NodeDesc.Type == asset2NodeDesc.Type) { diff3 = DiffNodeWithUniformType(baseNodeDesc.Type == asset1NodeDesc.Type ? baseNode : asset2Node, asset1Node, asset2Node); } else { diff3.InstanceType = asset1NodeDesc.Type; diff3.ChangeType = Diff3ChangeType.MergeFromAsset1; } diff3.FinalOverride = member1Override; } // If base changed from Sealed to non-sealed, and asset1 was base|sealed, change it back to plain base. if (baseOverride.IsSealed() && !member2Override.IsSealed() && member1Override == (OverrideType.Base | OverrideType.Sealed)) { diff3.FinalOverride = OverrideType.Base; } } } else { if (baseNodeDesc.Type != null && asset1NodeDesc.Type != null && asset2NodeDesc.Type != null) { // cases : base asset1 asset2 // case 1: T T T => Merge all instances if (baseNodeDesc.Type == asset1NodeDesc.Type && baseNodeDesc.Type == asset2NodeDesc.Type) { // If all types are the same, perform a normal diff. return(DiffNodeWithUniformType(baseNode, asset1Node, asset2Node)); } // case 2: T T1 T => Only from asset1 if (baseNodeDesc.Type != asset1NodeDesc.Type && baseNodeDesc.Type == asset2NodeDesc.Type) { diff3.ChangeType = Diff3ChangeType.MergeFromAsset1; diff3.InstanceType = asset1NodeDesc.Type; return(diff3); } // case 3: T T T1 => Only from asset2 if (baseNodeDesc.Type == asset1NodeDesc.Type && baseNodeDesc.Type != asset2NodeDesc.Type) { diff3.ChangeType = Diff3ChangeType.MergeFromAsset2; diff3.InstanceType = asset2NodeDesc.Type; return(diff3); } } else if (baseNodeDesc.Type != null && asset1NodeDesc.Type != null && baseNodeDesc.Type == asset1NodeDesc.Type) { // case 3: T T null => Merge from asset2 (set null on asset1) diff3.ChangeType = Diff3ChangeType.MergeFromAsset2; diff3.InstanceType = null; return(diff3); } // other cases: Always merge from asset1 (should be a conflict, but we assume that we can only use asset1 diff3.ChangeType = Diff3ChangeType.MergeFromAsset1; diff3.InstanceType = asset1NodeDesc.Type; return(diff3); } return(diff3); }
private static void MergeContainer(Diff3Node diff3Node, out object dataInstanceOut) { dataInstanceOut = null; // We don't have a valid parent (probably removed), so skip this node if (diff3Node.Parent != null && diff3Node.Parent.Asset1Node.Instance == null) { return; } if (diff3Node.Asset1Node == null) { diff3Node.Asset1Node = (diff3Node.Asset2Node ?? diff3Node.BaseNode).CreateWithEmptyInstance(); if (diff3Node.Parent != null) { diff3Node.Asset1Node.Parent = diff3Node.Parent.Asset1Node; } } object dataInstance = diff3Node.Asset1Node.Instance; // If a node has children, since DiffCollection/DiffDictionary takes null as empty arrays, // we should now create this array for it to be properly merged if (dataInstance == null) { dataInstanceOut = dataInstance = Activator.CreateInstance(diff3Node.InstanceType); } // If it's a collection, clear it and reconstruct it from DiffCollection result (stored in Diff3Node.Items) // TODO: Various optimizations to avoid removing and reinserting items that were already here before (would need to diff Asset1 and Diff3Node...) var collectionDescriptor = diff3Node.Asset1Node.InstanceDescriptor as CollectionDescriptor; if (collectionDescriptor != null && diff3Node.Type == Diff3NodeType.Collection) { collectionDescriptor.Clear(dataInstance); if (diff3Node.Items != null) { foreach (var item in diff3Node.Items) { object itemInstance; switch (item.ChangeType) { case Diff3ChangeType.Children: case Diff3ChangeType.Conflict: case Diff3ChangeType.ConflictType: // Use any valid object, it will be replaced later itemInstance = SafeNodeInstance(item.Asset1Node) ?? SafeNodeInstance(item.Asset2Node) ?? SafeNodeInstance(item.BaseNode); break; case Diff3ChangeType.None: case Diff3ChangeType.MergeFromAsset1: case Diff3ChangeType.MergeFromAsset1And2: itemInstance = item.Asset1Node.Instance; break; case Diff3ChangeType.MergeFromAsset2: itemInstance = item.Asset2Node.Instance; break; default: throw new InvalidOperationException(); } collectionDescriptor.Add(dataInstance, itemInstance); } } } }
private void DiffCollectionByIds(Diff3Node diff3, DataVisitNode baseNode, DataVisitNode asset1Node, DataVisitNode asset2Node) { DiffCollectionByIdsGeneric(diff3, baseNode, asset1Node, asset2Node, IdentifiableHelper.GetId, DiffNode); }
private void DiffCollection(Diff3Node diff3, DataVisitNode baseNode, DataVisitNode asset1Node, DataVisitNode asset2Node) { var baseItems = baseNode != null ? baseNode.Items ?? EmptyNodes : EmptyNodes; var asset1Items = asset1Node != null ? asset1Node.Items ?? EmptyNodes : EmptyNodes; var asset2Items = asset2Node != null ? asset2Node.Items ?? EmptyNodes : EmptyNodes; equalityComparer.Reset(); var changes = Diff3.Compare(baseItems, asset1Items, asset2Items, equalityComparer); foreach (var change in changes) { switch (change.ChangeType) { case SharpDiff.Diff3ChangeType.Equal: for (int i = 0; i < change.Base.Length; i++) { var diff3Node = new Diff3Node(baseItems[change.Base.From + i], asset1Items[change.From1.From + i], asset2Items[change.From2.From + i]) { ChangeType = Diff3ChangeType.None }; AddItem(diff3, diff3Node); } break; case SharpDiff.Diff3ChangeType.MergeFrom1: for (int i = 0; i < change.From1.Length; i++) { var diff3Node = new Diff3Node(null, asset1Items[change.From1.From + i], null) { ChangeType = Diff3ChangeType.MergeFromAsset1 }; AddItem(diff3, diff3Node); } break; case SharpDiff.Diff3ChangeType.MergeFrom2: for (int i = 0; i < change.From2.Length; i++) { var diff3Node = new Diff3Node(null, null, asset2Items[change.From2.From + i]) { ChangeType = Diff3ChangeType.MergeFromAsset2 }; AddItem(diff3, diff3Node); } break; case SharpDiff.Diff3ChangeType.MergeFrom1And2: for (int i = 0; i < change.From2.Length; i++) { var diff3Node = new Diff3Node(null, asset1Items[change.From1.From + i], asset2Items[change.From2.From + i]) { ChangeType = Diff3ChangeType.MergeFromAsset1And2 }; AddItem(diff3, diff3Node); } break; case SharpDiff.Diff3ChangeType.Conflict: int baseIndex = change.Base.IsValid ? change.Base.From : -1; int from1Index = change.From1.IsValid ? change.From1.From : -1; int from2Index = change.From2.IsValid ? change.From2.From : -1; // If there are changes only from 1 or 2 or base.Length == list1.Length == list2.Length, then try to make a diff per item // else output the conflict as a full conflict bool tryResolveConflict = false; if (baseIndex >= 0) { if (from1Index >= 0 && from2Index >= 0) { if ((change.Base.Length == change.From1.Length && change.Base.Length == change.From2.Length) || (change.From1.Length == change.From2.Length)) { tryResolveConflict = true; } } else if (from1Index >= 0) { tryResolveConflict = change.Base.Length == change.From1.Length; } else if (from2Index >= 0) { tryResolveConflict = change.Base.Length == change.From2.Length; } else { tryResolveConflict = true; } } // Iterate on items while ((baseIndex >= 0 && baseItems.Count > 0) || (from1Index >= 0 && asset1Items.Count > 0) || (from2Index >= 0 && asset2Items.Count > 0)) { var baseItem = GetSafeFromList(baseItems, ref baseIndex, ref change.Base); var asset1Item = GetSafeFromList(asset1Items, ref from1Index, ref change.From1); var asset2Item = GetSafeFromList(asset2Items, ref from2Index, ref change.From2); var diff3Node = tryResolveConflict ? DiffNode(baseItem, asset1Item, asset2Item) : new Diff3Node(baseItem, asset1Item, asset2Item) { ChangeType = Diff3ChangeType.Conflict }; AddItem(diff3, diff3Node); } break; } } // Order by descending index if (diff3.Items != null) { diff3.Items.Sort((left, right) => { int leftAsset1Index = left.Asset1Node != null ? ((DataVisitListItem)left.Asset1Node).Index : -1; int rightAsset1Index = right.Asset1Node != null ? ((DataVisitListItem)right.Asset1Node).Index : -1; return(rightAsset1Index.CompareTo(leftAsset1Index)); }); } }
private void DiffCollectionByIdsGeneric(Diff3Node diff3, DataVisitNode baseNode, DataVisitNode asset1Node, DataVisitNode asset2Node, Func <object, Guid> idGetter, Func <DataVisitNode, DataVisitNode, DataVisitNode, Diff3Node> diff3Getter) { var baseItems = baseNode != null ? baseNode.Items ?? EmptyNodes : EmptyNodes; var asset1Items = asset1Node != null ? asset1Node.Items ?? EmptyNodes : EmptyNodes; var asset2Items = asset2Node != null ? asset2Node.Items ?? EmptyNodes : EmptyNodes; // Pre-build dictionary var items = new Dictionary <Guid, Diff3CollectionByIdItem>(); for (int i = 0; i < baseItems.Count; i++) { var item = baseItems[i]; var id = GetSafeGuidForCollectionItem(item.Instance, i, idGetter); Diff3CollectionByIdItem entry; items.TryGetValue(id, out entry); entry.BaseIndex = i; items[id] = entry; } for (int i = 0; i < asset1Items.Count; i++) { var item = asset1Items[i]; var id = GetSafeGuidForCollectionItem(item.Instance, i, idGetter); Diff3CollectionByIdItem entry; items.TryGetValue(id, out entry); entry.Asset1Index = i; items[id] = entry; } for (int i = 0; i < asset2Items.Count; i++) { var item = asset2Items[i]; var id = GetSafeGuidForCollectionItem(item.Instance, i, idGetter); Diff3CollectionByIdItem entry; items.TryGetValue(id, out entry); entry.Asset2Index = i; items[id] = entry; } foreach (var idAndEntry in items.OrderBy(item => item.Value.Asset1Index ?? item.Value.Asset2Index ?? item.Value.BaseIndex ?? 0)) { var entry = idAndEntry.Value; bool hasBase = entry.BaseIndex.HasValue; bool hasAsset1 = entry.Asset1Index.HasValue; bool hasAsset2 = entry.Asset2Index.HasValue; int baseIndex = entry.BaseIndex ?? -1; int asset1Index = entry.Asset1Index ?? -1; int asset2Index = entry.Asset2Index ?? -1; if (hasBase && hasAsset1 && hasAsset2) { // If asset was already in base and is still valid for asset1 and asset2 var diff3Node = diff3Getter(baseNode.Items[baseIndex], asset1Node.Items[asset1Index], asset2Node.Items[asset2Index]); // Use index from asset2 if baseIndex = asset1Index, else use from asset1 index AddItemByPosition(diff3, diff3Node, baseIndex == asset1Index ? asset2Index : asset1Index, true); } else if (!hasBase) { // If no base, there is at least either asset1 or asset2 but not both, as they can't have the same id if (hasAsset1) { var diff3Node = new Diff3Node(null, asset1Node.Items[asset1Index], null) { ChangeType = Diff3ChangeType.MergeFromAsset1, InstanceType = asset1Node.Items[asset1Index].InstanceType }; AddItemByPosition(diff3, diff3Node, asset1Index, true); } else if (hasAsset2) { var diff3Node = new Diff3Node(null, null, asset2Node.Items[asset2Index]) { ChangeType = Diff3ChangeType.MergeFromAsset2, InstanceType = asset2Node.Items[asset2Index].InstanceType }; AddItemByPosition(diff3, diff3Node, asset2Index, true); } } else { // either item was removed from asset1 or asset2, so we assume that it is completely removed // (not strictly correct, because one item could change a sub-property, but we assume that when a top-level list item is removed, we remove it, even if it has changed internally) } } // The diff will only applied to children (we don't support members) diff3.ChangeType = Diff3ChangeType.Children; // Cleanup any hole in the list // If new items are found, just cleanup if (diff3.Items != null) { // Because in the previous loop, we can add some hole while trying to merge index (null nodes), we need to remove them from here. for (int i = diff3.Items.Count - 1; i >= 0; i--) { if (diff3.Items[i] == null) { diff3.Items.RemoveAt(i); } } } }