Index: Ringtoets/Common/src/Ringtoets.Common.Data/UpdateDataStrategies/UpdateDataStrategyBase.cs =================================================================== diff -u -re3ec5f4669022733a11c4c3070747331bf36056f -rc6b6a1a125535151416a5426d75472d045943d1a --- Ringtoets/Common/src/Ringtoets.Common.Data/UpdateDataStrategies/UpdateDataStrategyBase.cs (.../UpdateDataStrategyBase.cs) (revision e3ec5f4669022733a11c4c3070747331bf36056f) +++ Ringtoets/Common/src/Ringtoets.Common.Data/UpdateDataStrategies/UpdateDataStrategyBase.cs (.../UpdateDataStrategyBase.cs) (revision c6b6a1a125535151416a5426d75472d045943d1a) @@ -136,38 +136,22 @@ string sourceFilePath) { TTargetData[] importedObjects = importedDataCollection.ToArray(); - TTargetData[] objectsToBeAdded = GetObjectsToBeAdded(targetDataCollection, importedObjects).ToArray(); - TTargetData[] objectsToBeRemoved = GetObjectsToBeRemoved(targetDataCollection, importedObjects).ToArray(); - TTargetData[] objectsToBeUpdated = GetObjectsToBeUpdated(targetDataCollection, importedObjects).ToArray(); + var modification = new Modification(targetDataCollection, importedObjects, equalityComparer); + var affectedObjects = new List(); - if (objectsToBeAdded.Any() || objectsToBeRemoved.Any() || objectsToBeUpdated.Any()) + if (modification.HasUpdates()) { affectedObjects.Add(targetDataCollection); } - affectedObjects.AddRange(UpdateData(objectsToBeUpdated, importedObjects)); - affectedObjects.AddRange(RemoveData(objectsToBeRemoved)); + affectedObjects.AddRange(UpdateData(modification.ObjectsToBeUpdated.Values, importedObjects)); + affectedObjects.AddRange(RemoveData(modification.ObjectsToBeRemoved)); targetDataCollection.Clear(); - targetDataCollection.AddRange(objectsToBeUpdated.Union(objectsToBeAdded), sourceFilePath); + targetDataCollection.AddRange(modification.GetModifiedCollection(), sourceFilePath); return affectedObjects.Distinct(new ReferenceEqualityComparer()); } - private IEnumerable GetObjectsToBeRemoved(IEnumerable existingCollection, IEnumerable importedDataOjects) - { - return existingCollection.Except(importedDataOjects, equalityComparer); - } - - private IEnumerable GetObjectsToBeUpdated(IEnumerable existingCollection, IEnumerable importedDataObjects) - { - return existingCollection.Intersect(importedDataObjects, equalityComparer); - } - - private IEnumerable GetObjectsToBeAdded(IEnumerable existingCollection, IEnumerable importedDataObjects) - { - return importedDataObjects.Where(source => !existingCollection.Contains(source, equalityComparer)); - } - /// /// Updates all the objects and their dependent data that needs to be /// updated with data from the imported data collection. @@ -221,5 +205,129 @@ } return affectedObjects; } + + /// + /// Inner class for obtaining the modifications of an update action. + /// + private class Modification + { + /// + /// Creates a new instance of . + /// + /// The current collection of objects. + /// The collection of objects that were imported. + /// The comparer to test whether elements in + /// have a matching element in + /// . + public Modification(IEnumerable existingObjects, + IEnumerable updatedObjects, + IEqualityComparer equalityComparer) + { + TTargetData[] existingArray = existingObjects.ToArray(); + + var index = 0; + foreach (TTargetData importedObject in updatedObjects) + { + int existingObjectIndex = FindIndex(existingArray, importedObject, equalityComparer); + if (existingObjectIndex > -1) + { + ObjectsToBeUpdated.Add(index, existingArray[existingObjectIndex]); + existingArray[existingObjectIndex] = null; + } + else + { + ObjectsToBeAdded.Add(index, importedObject); + } + index++; + } + + ObjectsToBeRemoved = existingArray.Where(e => e != null); + } + + /// + /// Gets the objects that were updated. + /// + public Dictionary ObjectsToBeUpdated { get; } = new Dictionary(); + + /// + /// Gets the objects that were removed. + /// + public IEnumerable ObjectsToBeRemoved { get; } + + /// + /// Gets a collection of updated objects from the existing object collection and the + /// added objects from the imported object collection, in the same order that was + /// found in the imported object collection. + /// + /// An ordered collection of updated and added elements. + public IEnumerable GetModifiedCollection() + { + TTargetData[] remainingObjects = ObjectsToBeUpdated.Values.Union(ObjectsToBeAdded.Values).ToArray(); + int[] indices = GetElementOrder(); + + foreach (int i in Enumerable.Range(0, remainingObjects.Length)) + { + TTargetData x = remainingObjects[i]; + int j = i; + while (true) + { + int k = indices[j]; + indices[j] = j; + if (k == i) + { + break; + } + remainingObjects[j] = remainingObjects[k]; + j = k; + } + remainingObjects[j] = x; + } + + return remainingObjects; + } + + /// + /// Finds out whether there was a difference between the existing and the imported + /// object collections. + /// + /// + public bool HasUpdates() + { + return ObjectsToBeRemoved.Any() || ObjectsToBeAdded.Any() || ObjectsToBeUpdated.Any(); + } + + private Dictionary ObjectsToBeAdded { get; } = new Dictionary(); + + private int[] GetElementOrder() + { + int[] keys = ObjectsToBeUpdated.Keys.Union(ObjectsToBeAdded.Keys).ToArray(); + + int orderLength = keys.Length; + var order = new int[orderLength]; + for (var valueToInsert = 0; valueToInsert < orderLength; valueToInsert++) + { + order[keys[valueToInsert]] = valueToInsert; + } + + return order; + } + + private static int FindIndex(TTargetData[] collectionToLookIn, TTargetData objectToFind, IEqualityComparer equalityComparer) + { + if (objectToFind == null) + { + throw new ArgumentNullException(nameof(objectToFind)); + } + for (var i = 0; i < collectionToLookIn.Length; i++) + { + TTargetData targetData = collectionToLookIn[i]; + if (targetData != null && equalityComparer.Equals(targetData, objectToFind)) + { + return i; + } + } + return -1; + } + } } } \ No newline at end of file Index: Ringtoets/Common/test/Ringtoets.Common.Data.Test/UpdateDataStrategies/UpdateDataStrategyBaseTest.cs =================================================================== diff -u -r6a28e1aa3e370ae901e184ce6b511b0271228d13 -rc6b6a1a125535151416a5426d75472d045943d1a --- Ringtoets/Common/test/Ringtoets.Common.Data.Test/UpdateDataStrategies/UpdateDataStrategyBaseTest.cs (.../UpdateDataStrategyBaseTest.cs) (revision 6a28e1aa3e370ae901e184ce6b511b0271228d13) +++ Ringtoets/Common/test/Ringtoets.Common.Data.Test/UpdateDataStrategies/UpdateDataStrategyBaseTest.cs (.../UpdateDataStrategyBaseTest.cs) (revision c6b6a1a125535151416a5426d75472d045943d1a) @@ -443,6 +443,38 @@ } [Test] + public void UpdateTargetCollectionData_CollectionOrderChangedAndElementAdded_UpdatesCollectionInCorrectOrder() + { + // Setup + var currentCollection = new[] + { + new TestItem("Item one"), + new TestItem("Item two"), + new TestItem("Item three") + }; + var collection = new TestUniqueItemCollection(); + collection.AddRange(currentCollection, sourceFilePath); + + var importedItems = new[] + { + new TestItem("Item three"), + new TestItem("Item four"), + new TestItem("Item two"), + new TestItem("Item one") + }; + + var strategy = new TestUpdateDataStrategy(new TestFailureMechanism()); + + // Call + strategy.ConcreteUpdateData(collection, + importedItems, + sourceFilePath); + + // Assert + CollectionAssert.AreEqual(importedItems, collection); + } + + [Test] public void UpdateTargetCollectionData_CollectionNotEmptyAndImportedDataHasDuplicateDefinitions_ThrowsUpdateDataException() { // Setup Index: Ringtoets/GrassCoverErosionInwards/test/Ringtoets.GrassCoverErosionInwards.Plugin.Test/FileImporters/GrassCoverErosionInwardsDikeProfileUpdateDataStrategyTest.cs =================================================================== diff -u -r21a8ed792a5a1fca14173225ba3ac976e40f7a1c -rc6b6a1a125535151416a5426d75472d045943d1a --- Ringtoets/GrassCoverErosionInwards/test/Ringtoets.GrassCoverErosionInwards.Plugin.Test/FileImporters/GrassCoverErosionInwardsDikeProfileUpdateDataStrategyTest.cs (.../GrassCoverErosionInwardsDikeProfileUpdateDataStrategyTest.cs) (revision 21a8ed792a5a1fca14173225ba3ac976e40f7a1c) +++ Ringtoets/GrassCoverErosionInwards/test/Ringtoets.GrassCoverErosionInwards.Plugin.Test/FileImporters/GrassCoverErosionInwardsDikeProfileUpdateDataStrategyTest.cs (.../GrassCoverErosionInwardsDikeProfileUpdateDataStrategyTest.cs) (revision c6b6a1a125535151416a5426d75472d045943d1a) @@ -393,19 +393,19 @@ Assert.AreEqual(2, dikeProfiles.Count); var expectedDikeProfiles = new[] { - dikeProfileToBeUpdated, - dikeProfileToBeAdded + dikeProfileToBeAdded, + dikeProfileToBeUpdated }; CollectionAssert.AreEqual(expectedDikeProfiles, dikeProfiles); - DikeProfile updatedDikeProfile = dikeProfiles[0]; - Assert.AreSame(dikeProfileToBeUpdated, updatedDikeProfile); - AssertDikeProfile(dikeProfileToUpdateFrom, updatedDikeProfile); - - DikeProfile addedDikeProfile = dikeProfiles[1]; + DikeProfile addedDikeProfile = dikeProfiles[0]; Assert.AreSame(dikeProfileToBeAdded, addedDikeProfile); AssertDikeProfile(dikeProfileToBeAdded, addedDikeProfile); + DikeProfile updatedDikeProfile = dikeProfiles[1]; + Assert.AreSame(dikeProfileToBeUpdated, updatedDikeProfile); + AssertDikeProfile(dikeProfileToUpdateFrom, updatedDikeProfile); + CollectionAssert.AreEquivalent(new IObservable[] { dikeProfileToBeUpdated,