public void ReconcileWithBase(AssetNode rootNode)
        {
            var visitor = CreateReconcilierVisitor();

            visitor.Visiting += (node, path) => ReconcileWithBaseNode((AssetNode)node);
            visitor.Visit(rootNode);
        }
        protected internal override object CloneValueFromBase(object value, AssetNode node)
        {
            var part = value as TAssetPart;

            // Part reference
            if (part != null)
            {
                // We need to find out for which entity we are cloning this (other) entity
                var multiContentNode = node as MultiContentNode;
                var owner            = (TAssetPartDesign)multiContentNode?.GetContent(NodesToOwnerPartVisitor.OwnerPartContentName).Retrieve();
                if (owner != null)
                {
                    // Then instead of creating a clone, we just return the corresponding part in this asset (in term of base and base instance)
                    var partInDerived = AssetHierarchy.Hierarchy.Parts.FirstOrDefault(x => x.Base?.BasePartId == part.Id && x.Base?.InstanceId == owner.Base?.InstanceId);
                    return(partInDerived?.Part);
                }
            }

            var result = base.CloneValueFromBase(value, node);

            return(result);
        }
Exemple #3
0
        public static void ApplyOverrides(AssetNode rootNode, IDictionary <YamlAssetPath, OverrideType> overrides)
        {
            if (rootNode == null)
            {
                throw new ArgumentNullException(nameof(rootNode));
            }

            if (overrides == null)
            {
                return;
            }

            foreach (var overrideInfo in overrides)
            {
                Index index;
                bool  overrideOnKey;
                var   node = rootNode.ResolveObjectPath(overrideInfo.Key, out index, out overrideOnKey);
                // The node is unreachable, skip this override.
                if (node == null)
                {
                    continue;
                }

                if (index == Index.Empty)
                {
                    node.SetContentOverride(overrideInfo.Value);
                }
                else if (!overrideOnKey)
                {
                    node.SetItemOverride(overrideInfo.Value, index);
                }
                else
                {
                    node.SetKeyOverride(overrideInfo.Value, index);
                }
            }
        }
Exemple #4
0
        private void ReconcileWithBaseNode(AssetNode assetNode)
        {
            if (assetNode.Content is ObjectContent || assetNode.BaseContent == null || !assetNode.CanOverride)
            {
                return;
            }

            var baseNode   = (AssetNode)assetNode.BaseContent.OwnerNode;
            var localValue = assetNode.Content.Retrieve();
            var baseValue  = assetNode.BaseContent.Retrieve();

            // Reconcile occurs only when the node is not overridden.
            if (!assetNode.IsContentOverridden())
            {
                assetNode.ResettingOverride = true;
                // Handle null cases first
                if (localValue == null || baseValue == null)
                {
                    if (localValue == null && baseValue != null)
                    {
                        var clonedValue = assetNode.Cloner(baseValue);
                        assetNode.Content.Update(clonedValue);
                    }
                    else if (localValue != null /*&& baseValue == null*/)
                    {
                        assetNode.Content.Update(null);
                    }
                }
                // Then handle collection and dictionary cases
                else if (assetNode.Content.Descriptor is CollectionDescriptor || assetNode.Content.Descriptor is DictionaryDescriptor)
                {
                    // Items to add and to remove are stored in local collections and processed later, since they might affect indices
                    var itemsToRemove = new List <ItemId>();
                    var itemsToAdd    = new SortedList <object, ItemId>(new DefaultKeyComparer());

                    // Check for item present in the instance and absent from the base.
                    foreach (var index in assetNode.Content.Indices)
                    {
                        // Skip overridden items
                        if (assetNode.IsItemOverridden(index))
                        {
                            continue;
                        }

                        var itemId = assetNode.IndexToId(index);
                        if (itemId != ItemId.Empty)
                        {
                            // Look if an item with the same id exists in the base.
                            if (!baseNode.HasId(itemId))
                            {
                                // If not, remove this item from the instance.
                                itemsToRemove.Add(itemId);
                            }
                        }
                        else
                        {
                            // This case should not happen, but if we have an empty id due to corrupted data let's just remove the item.
                            itemsToRemove.Add(itemId);
                        }
                    }

                    // Clean items marked as "override-deleted" that are absent from the base.
                    var ids = CollectionItemIdHelper.GetCollectionItemIds(localValue);
                    foreach (var deletedId in ids.DeletedItems.ToList())
                    {
                        if (assetNode.BaseContent.Indices.All(x => baseNode.IndexToId(x) != deletedId))
                        {
                            ids.UnmarkAsDeleted(deletedId);
                        }
                    }

                    // Add item present in the base and missing here, and also update items that have different values between base and instance
                    foreach (var index in assetNode.BaseContent.Indices)
                    {
                        var itemId = baseNode.IndexToId(index);
                        // TODO: What should we do if it's empty? It can happen only from corrupted data

                        // Skip items marked as "override-deleted"
                        if (itemId == ItemId.Empty || assetNode.IsItemDeleted(itemId))
                        {
                            continue;
                        }

                        Index localIndex;
                        if (!assetNode.TryIdToIndex(itemId, out localIndex))
                        {
                            // We have an item in the base that is missing in the instance (not even marked as "override-deleted")
                            if (assetNode.Content.Descriptor is DictionaryDescriptor && (assetNode.Content.Reference?.HasIndex(index) == true || assetNode.Content.Indices.Any(x => index.Equals(x))))
                            {
                                // For dictionary, we might have a key collision, if so, we consider that the new value from the base is deleted in the instance.
                                var instanceIds = CollectionItemIdHelper.GetCollectionItemIds(assetNode.Content.Retrieve());
                                instanceIds.MarkAsDeleted(itemId);
                            }
                            else
                            {
                                // Add it if the key is available for add
                                itemsToAdd.Add(index.Value, itemId);
                            }
                        }
                        else
                        {
                            // If the item is present in both the instance and the base, check if we need to reconcile the value
                            var member     = assetNode.Content as MemberContent;
                            var targetNode = assetNode.Content.Reference?.AsEnumerable?[localIndex]?.TargetNode;
                            // Skip it if it's overridden
                            if (!assetNode.IsItemOverridden(localIndex))
                            {
                                var localItemValue = assetNode.Content.Retrieve(localIndex);
                                var baseItemValue  = baseNode.Content.Retrieve(index);
                                if (ShouldReconcileItem(member, targetNode, localItemValue, baseItemValue, assetNode.Content.Reference is ReferenceEnumerable))
                                {
                                    var clonedValue = assetNode.Cloner(baseItemValue);
                                    assetNode.Content.Update(clonedValue, localIndex);
                                }
                            }
                            // In dictionaries, the keys might be different between the instance and the base. We need to reconcile them too
                            if (assetNode.Content.Descriptor is DictionaryDescriptor && !assetNode.IsKeyOverridden(localIndex))
                            {
                                if (ShouldReconcileItem(member, targetNode, localIndex.Value, index.Value, false))
                                {
                                    // Reconcile using a move (Remove + Add) of the key-value pair
                                    var clonedIndex    = new Index(assetNode.Cloner(index.Value));
                                    var localItemValue = assetNode.Content.Retrieve(localIndex);
                                    assetNode.Content.Remove(localItemValue, localIndex);
                                    assetNode.Content.Add(localItemValue, clonedIndex);
                                    ids[clonedIndex.Value] = itemId;
                                }
                            }
                        }
                    }

                    // Process items marked to be removed
                    foreach (var item in itemsToRemove)
                    {
                        var index = assetNode.IdToIndex(item);
                        var value = assetNode.Content.Retrieve(index);
                        assetNode.Content.Remove(value, index);
                        // We're reconciling, so let's hack the normal behavior of marking the removed item as deleted.
                        ids.UnmarkAsDeleted(item);
                    }

                    // Process items marked to be added
                    foreach (var item in itemsToAdd)
                    {
                        var baseIndex     = baseNode.IdToIndex(item.Value);
                        var baseItemValue = baseNode.Content.Retrieve(baseIndex);
                        var clonedValue   = assetNode.Cloner(baseItemValue);
                        if (assetNode.Content.Descriptor is CollectionDescriptor)
                        {
                            // In a collection, we need to find an index that matches the index on the base to maintain order.
                            // To do so, we iterate from the index in the base to zero.
                            var currentBaseIndex = baseIndex.Int - 1;

                            // Initialize the target index to zero, in case we don't find any better index.
                            var localIndex = new Index(0);

                            // Find the first item of the base that also exists (in term of id) in the local node, iterating backward (from baseIndex to 0)
                            while (currentBaseIndex >= 0)
                            {
                                ItemId baseId;
                                // This should not happen since the currentBaseIndex comes from the base.
                                if (!baseNode.TryIndexToId(new Index(currentBaseIndex), out baseId))
                                {
                                    throw new InvalidOperationException("Cannot find an identifier matching the index in the base collection");
                                }

                                Index sameIndexInInstance;
                                // If we have an matching item, we want to insert right after it
                                if (assetNode.TryIdToIndex(baseId, out sameIndexInInstance))
                                {
                                    localIndex = new Index(sameIndexInInstance.Int + 1);
                                    break;
                                }
                                currentBaseIndex--;
                            }

                            assetNode.Restore(clonedValue, localIndex, item.Value);
                        }
                        else
                        {
                            // This case is for dictionary. Key collisions have already been handle at that point so we can directly do the add without further checks.
                            assetNode.Restore(clonedValue, baseIndex, item.Value);
                        }
                    }
                }
                // Finally, handle single properties
                else
                {
                    var member     = assetNode.Content as MemberContent;
                    var targetNode = assetNode.Content.Reference?.AsObject?.TargetNode;
                    if (ShouldReconcileItem(member, targetNode, localValue, baseValue, assetNode.Content.Reference is ObjectReference))
                    {
                        var clonedValue = assetNode.Cloner(baseValue);
                        assetNode.Content.Update(clonedValue);
                    }
                }
                assetNode.ResettingOverride = false;
            }
        }
Exemple #5
0
 // TODO: turn protected
 protected virtual object CloneValueFromBase(object value, AssetNode node)
 {
     return(AssetNode.CloneFromBase(value));
 }
Exemple #6
0
 // TODO: turn private
 public void LinkToBase(AssetNode sourceRootNode, AssetNode targetRootNode)
 {
     baseLinker.ShouldVisit = (member, node) => (node == sourceRootNode || !baseLinkedNodes.ContainsKey((AssetNode)node)) && ShouldListenToTargetNode(member, node);
     baseLinker.LinkGraph(sourceRootNode, targetRootNode);
 }
        private void ReconcileWithBaseNode(AssetNode assetNode)
        {
            if (assetNode.Content is ObjectContent || assetNode.BaseContent == null || !assetNode.CanOverride)
                return;

            var baseNode = (AssetNode)assetNode.BaseContent.OwnerNode;
            var localValue = assetNode.Content.Retrieve();
            var baseValue = assetNode.BaseContent.Retrieve();

            // Reconcile occurs only when the node is not overridden.
            if (!assetNode.IsContentOverridden())
            {
                assetNode.ResettingOverride = true;
                // Handle null cases first
                if (localValue == null || baseValue == null)
                {
                    if (localValue == null && baseValue != null)
                    {
                        var clonedValue = assetNode.Cloner(baseValue);
                        assetNode.Content.Update(clonedValue);
                    }
                    else if (localValue != null /*&& baseValue == null*/)
                    {
                        assetNode.Content.Update(null);
                    }
                }
                // Then handle collection and dictionary cases
                else if (assetNode.Content.Descriptor is CollectionDescriptor || assetNode.Content.Descriptor is DictionaryDescriptor)
                {
                    // Items to add and to remove are stored in local collections and processed later, since they might affect indices
                    var itemsToRemove = new List<ItemId>();
                    var itemsToAdd = new SortedList<object, ItemId>(new DefaultKeyComparer());

                    // Check for item present in the instance and absent from the base.
                    foreach (var index in assetNode.Content.Indices)
                    {
                        // Skip overridden items
                        if (assetNode.IsItemOverridden(index))
                            continue;

                        var itemId = assetNode.IndexToId(index);
                        if (itemId != ItemId.Empty)
                        {
                            // Look if an item with the same id exists in the base.
                            if (!baseNode.HasId(itemId))
                            {
                                // If not, remove this item from the instance.
                                itemsToRemove.Add(itemId);
                            }
                        }
                        else
                        {
                            // This case should not happen, but if we have an empty id due to corrupted data let's just remove the item.
                            itemsToRemove.Add(itemId);
                        }
                    }

                    // Clean items marked as "override-deleted" that are absent from the base.
                    var ids = CollectionItemIdHelper.GetCollectionItemIds(localValue);
                    foreach (var deletedId in ids.DeletedItems.ToList())
                    {
                        if (assetNode.BaseContent.Indices.All(x => baseNode.IndexToId(x) != deletedId))
                        {
                            ids.UnmarkAsDeleted(deletedId);
                        }
                    }

                    // Add item present in the base and missing here, and also update items that have different values between base and instance
                    foreach (var index in assetNode.BaseContent.Indices)
                    {
                        var itemId = baseNode.IndexToId(index);
                        // TODO: What should we do if it's empty? It can happen only from corrupted data

                        // Skip items marked as "override-deleted"
                        if (itemId == ItemId.Empty || assetNode.IsItemDeleted(itemId))
                            continue;

                        Index localIndex;
                        if (!assetNode.TryIdToIndex(itemId, out localIndex))
                        {
                            // We have an item in the base that is missing in the instance (not even marked as "override-deleted")
                            if (assetNode.Content.Descriptor is DictionaryDescriptor && (assetNode.Content.Reference?.HasIndex(index) == true || assetNode.Content.Indices.Any(x => index.Equals(x))))
                            {
                                // For dictionary, we might have a key collision, if so, we consider that the new value from the base is deleted in the instance.
                                var instanceIds = CollectionItemIdHelper.GetCollectionItemIds(assetNode.Content.Retrieve());
                                instanceIds.MarkAsDeleted(itemId);
                            }
                            else
                            {
                                // Add it if the key is available for add
                                itemsToAdd.Add(index.Value, itemId);
                            }
                        }
                        else
                        {
                            // If the item is present in both the instance and the base, check if we need to reconcile the value
                            var member = assetNode.Content as MemberContent;
                            var targetNode = assetNode.Content.Reference?.AsEnumerable?[localIndex]?.TargetNode;
                            // Skip it if it's overridden
                            if (!assetNode.IsItemOverridden(localIndex))
                            {
                                var localItemValue = assetNode.Content.Retrieve(localIndex);
                                var baseItemValue = baseNode.Content.Retrieve(index);
                                if (ShouldReconcileItem(member, targetNode, localItemValue, baseItemValue, assetNode.Content.Reference is ReferenceEnumerable))
                                {
                                    var clonedValue = assetNode.Cloner(baseItemValue);
                                    assetNode.Content.Update(clonedValue, localIndex);
                                }
                            }
                            // In dictionaries, the keys might be different between the instance and the base. We need to reconcile them too
                            if (assetNode.Content.Descriptor is DictionaryDescriptor && !assetNode.IsKeyOverridden(localIndex))
                            {
                                if (ShouldReconcileItem(member, targetNode, localIndex.Value, index.Value, false))
                                {
                                    // Reconcile using a move (Remove + Add) of the key-value pair
                                    var clonedIndex = new Index(assetNode.Cloner(index.Value));
                                    var localItemValue = assetNode.Content.Retrieve(localIndex);
                                    assetNode.Content.Remove(localItemValue, localIndex);
                                    assetNode.Content.Add(localItemValue, clonedIndex);
                                    ids[clonedIndex.Value] = itemId;
                                }
                            }
                        }
                    }

                    // Process items marked to be removed
                    foreach (var item in itemsToRemove)
                    {
                        var index = assetNode.IdToIndex(item);
                        var value = assetNode.Content.Retrieve(index);
                        assetNode.Content.Remove(value, index);
                        // We're reconciling, so let's hack the normal behavior of marking the removed item as deleted.
                        ids.UnmarkAsDeleted(item);
                    }

                    // Process items marked to be added
                    foreach (var item in itemsToAdd)
                    {
                        var baseIndex = baseNode.IdToIndex(item.Value);
                        var baseItemValue = baseNode.Content.Retrieve(baseIndex);
                        var clonedValue = assetNode.Cloner(baseItemValue);
                        if (assetNode.Content.Descriptor is CollectionDescriptor)
                        {
                            // In a collection, we need to find an index that matches the index on the base to maintain order.
                            // To do so, we iterate from the index in the base to zero.
                            var currentBaseIndex = baseIndex.Int - 1;

                            // Initialize the target index to zero, in case we don't find any better index.
                            var localIndex = new Index(0);
                            
                            // Find the first item of the base that also exists (in term of id) in the local node, iterating backward (from baseIndex to 0)
                            while (currentBaseIndex >= 0)
                            {
                                ItemId baseId;
                                // This should not happen since the currentBaseIndex comes from the base.
                                if (!baseNode.TryIndexToId(new Index(currentBaseIndex), out baseId))
                                    throw new InvalidOperationException("Cannot find an identifier matching the index in the base collection");

                                Index sameIndexInInstance;
                                // If we have an matching item, we want to insert right after it
                                if (assetNode.TryIdToIndex(baseId, out sameIndexInInstance))
                                {
                                    localIndex = new Index(sameIndexInInstance.Int + 1);
                                    break;
                                }
                                currentBaseIndex--;
                            }

                            assetNode.Restore(clonedValue, localIndex, item.Value);
                        }
                        else
                        {
                            // This case is for dictionary. Key collisions have already been handle at that point so we can directly do the add without further checks.
                            assetNode.Restore(clonedValue, baseIndex, item.Value);
                        }
                    }
                }
                // Finally, handle single properties
                else
                {
                    var member = assetNode.Content as MemberContent;
                    var targetNode = assetNode.Content.Reference?.AsObject?.TargetNode;
                    if (ShouldReconcileItem(member, targetNode, localValue, baseValue, assetNode.Content.Reference is ObjectReference))
                    {
                        var clonedValue = assetNode.Cloner(baseValue);
                        assetNode.Content.Update(clonedValue);
                    }
                }
                assetNode.ResettingOverride = false;
            }
        }
 // TODO: turn protected
 protected virtual object CloneValueFromBase(object value, AssetNode node)
 {
     return AssetNode.CloneFromBase(value);
 }
 // TODO: turn private
 public void LinkToBase(AssetNode sourceRootNode, AssetNode targetRootNode)
 {
     baseLinker.ShouldVisit = (member, node) => (node == sourceRootNode || !baseLinkedNodes.ContainsKey((AssetNode)node)) && ShouldListenToTargetNode(member, node);
     baseLinker.LinkGraph(sourceRootNode, targetRootNode);
 }
        public static void ApplyOverrides(AssetNode rootNode, IDictionary<YamlAssetPath, OverrideType> overrides)
        {
            if (rootNode == null) throw new ArgumentNullException(nameof(rootNode));

            if (overrides == null)
                return;

            foreach (var overrideInfo in overrides)
            {
                Index index;
                bool overrideOnKey;
                var node = rootNode.ResolveObjectPath(overrideInfo.Key, out index, out overrideOnKey);
                // The node is unreachable, skip this override.
                if (node == null)
                    continue;

                if (index == Index.Empty)
                {
                    node.SetContentOverride(overrideInfo.Value);
                }
                else if (!overrideOnKey)
                {
                    node.SetItemOverride(overrideInfo.Value, index);
                }
                else
                {
                    node.SetKeyOverride(overrideInfo.Value, index);
                }
            }
        }
 // TODO: this method is should be called in every scenario of ReconcileWithBase, it is not the case yet.
 protected virtual bool CanUpdate(AssetNode node, ContentChangeType changeType, Index index, object value)
 {
     return(true);
 }