// Copyright (C) Stichting Deltares 2017. All rights reserved. // // This file is part of Ringtoets. // // Ringtoets is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // All names, logos, and references to "Deltares" are registered trademarks of // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. using System; using System.Collections.Generic; using System.Linq; using Core.Common.Base; using Core.Common.Utils; using Ringtoets.Common.Data.Exceptions; using Ringtoets.Common.Data.FailureMechanism; using Ringtoets.Common.Data.Properties; namespace Ringtoets.Common.Data.UpdateDataStrategies { /// /// Strategy for updating the current collection and dependent data with imported data: /// /// Adds imported items that are not part of the current collection. /// Removes items that are part of the current collection, but are not part of the imported item collection. /// Updates the items that are part of the current collection and are part of the imported item collection. /// /// /// The target data type. /// The failure mechanism in which the target collection should be updated. public abstract class UpdateDataStrategyBase where TTargetData : class where TFailureMechanism : IFailureMechanism { protected readonly TFailureMechanism FailureMechanism; private readonly IEqualityComparer equalityComparer; /// /// Creates a new instance of object. /// /// The failure mechanism which needs to be updated. /// The comparer which should be used to determine when two objects are equal. /// Thrown when any input parameter is null. protected UpdateDataStrategyBase(TFailureMechanism failureMechanism, IEqualityComparer equalityComparer) { if (failureMechanism == null) { throw new ArgumentNullException(nameof(failureMechanism)); } if (equalityComparer == null) { throw new ArgumentNullException(nameof(equalityComparer)); } this.equalityComparer = equalityComparer; FailureMechanism = failureMechanism; } /// /// Updates the object and its dependent data with data from the imported data. /// /// Object that needs to be updated. /// The object to update from. /// An with affected objects. /// Thrown when duplicate items are found. protected abstract IEnumerable UpdateObjectAndDependentData(TTargetData objectToUpdate, TTargetData objectToUpdateFrom); /// /// Removes the objects and their dependent data. /// /// The object that is removed. /// An with affected objects. protected abstract IEnumerable RemoveObjectAndDependentData(TTargetData removedObject); /// /// Updates the items and their associated data within the target collection with the data contained /// in the imported data collection. /// /// The target collection that needs to be updated. /// The imported data collection that is used to update /// the . /// The source file path. /// A of affected objects. /// Thrown when an error occurred while updating the data. protected IEnumerable UpdateTargetCollectionData(ObservableUniqueItemCollectionWithSourcePath targetDataCollection, IEnumerable importedDataCollection, string sourceFilePath) { if (targetDataCollection == null) { throw new ArgumentNullException(nameof(targetDataCollection)); } if (importedDataCollection == null) { throw new ArgumentNullException(nameof(importedDataCollection)); } if (sourceFilePath == null) { throw new ArgumentNullException(nameof(sourceFilePath)); } try { return ModifyDataCollection(targetDataCollection, importedDataCollection, sourceFilePath); } catch (ArgumentException e) { throw new UpdateDataException(e.Message, e); } } /// /// Identifies which items were changed, removed and added to the target collection /// when compared with the imported data and performs the necessary operations for /// the dependent data of the affected elements. /// /// The target data collection which needs to be updated. /// The imported data collection which is used to update /// the /// The source file path. /// A with affected objects. /// Thrown when duplicate items are being added to the /// . /// Thrown when duplicate items are found in the /// . private IEnumerable ModifyDataCollection(ObservableUniqueItemCollectionWithSourcePath targetDataCollection, IEnumerable importedDataCollection, 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 affectedObjects = new List(); if (objectsToBeAdded.Any() || objectsToBeRemoved.Any() || objectsToBeUpdated.Any()) { affectedObjects.Add(targetDataCollection); } affectedObjects.AddRange(UpdateData(objectsToBeUpdated, importedObjects)); affectedObjects.AddRange(RemoveData(objectsToBeRemoved)); targetDataCollection.Clear(); targetDataCollection.AddRange(objectsToBeUpdated.Union(objectsToBeAdded), 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. /// /// The objects that need to be updated. /// The data to update from. /// A of affected items. /// Thrown when the imported /// contains duplicate items. private IEnumerable UpdateData(IEnumerable objectsToUpdate, IEnumerable importedDataCollection) { var affectedObjects = new List(); if (importedDataCollection.Count() != importedDataCollection.Distinct(equalityComparer).Count()) { throw new UpdateDataException(Resources.UpdateDataStrategyBase_UpdateTargetCollectionData_Imported_data_must_contain_unique_items); } foreach (TTargetData objectToUpdate in objectsToUpdate) { TTargetData objectToUpdateFrom = importedDataCollection.Single(importedObject => equalityComparer.Equals(importedObject, objectToUpdate)); affectedObjects.AddRange(UpdateObjectAndDependentData(objectToUpdate, objectToUpdateFrom)); } return affectedObjects; } /// /// Removes all the objects and their dependent data. /// /// The objects that need to be removed. /// A of affected items. private IEnumerable RemoveData(IEnumerable objectsToRemove) { var affectedObjects = new List(); foreach (TTargetData objectToRemove in objectsToRemove) { affectedObjects.AddRange(RemoveObjectAndDependentData(objectToRemove)); } return affectedObjects; } } }