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