using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Runtime.Serialization; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Models { /// /// Represents a collection of objects /// [Serializable] [DataContract] // TODO: Change this to ObservableDictionary so we can reduce the INotifyCollectionChanged implementation details public class PropertyGroupCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable { /// /// Initializes a new instance of the class. /// public PropertyGroupCollection() { } /// /// Initializes a new instance of the class. /// /// The groups. public PropertyGroupCollection(IEnumerable groups) => Reset(groups); /// /// Resets the collection to only contain the instances referenced in the parameter. /// /// The property groups. /// internal void Reset(IEnumerable groups) { // Collection events will be raised in each of these calls Clear(); // Collection events will be raised in each of these calls foreach (var group in groups) Add(group); } protected override void SetItem(int index, PropertyGroup item) { var oldItem = index >= 0 ? this[index] : item; base.SetItem(index, item); oldItem.Collection = null; item.Collection = this; OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, oldItem)); } protected override void RemoveItem(int index) { var removed = this[index]; base.RemoveItem(index); removed.Collection = null; OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); } protected override void InsertItem(int index, PropertyGroup item) { base.InsertItem(index, item); item.Collection = this; OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } protected override void ClearItems() { foreach (var item in this) { item.Collection = null; } base.ClearItems(); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } public new void Add(PropertyGroup item) { // Ensure alias is set if (string.IsNullOrEmpty(item.Alias)) { throw new InvalidOperationException("Set an alias before adding the property group."); } // Note this is done to ensure existing groups can be renamed if (item.HasIdentity && item.Id > 0) { var index = IndexOfKey(item.Id); if (index != -1) { var keyExists = Contains(item.Alias); if (keyExists) throw new ArgumentException($"Naming conflict: changing the alias of property group '{item.Name}' would result in duplicates."); // Collection events will be raised in SetItem SetItem(index, item); return; } } else { var index = IndexOfKey(item.Alias); if (index != -1) { // Collection events will be raised in SetItem SetItem(index, item); return; } } // Collection events will be raised in InsertItem base.Add(item); } internal void ChangeKey(PropertyGroup item, string newKey) => ChangeItemKey(item, newKey); public bool Contains(int id) => this.IndexOfKey(id) != -1; public int IndexOfKey(string key) => this.FindIndex(x => x.Alias == key); public int IndexOfKey(int id) => this.FindIndex(x => x.Id == id); protected override string GetKeyForItem(PropertyGroup item) => item.Alias; public event NotifyCollectionChangedEventHandler? CollectionChanged; /// /// Clears all event handlers /// public void ClearCollectionChangedEvents() => CollectionChanged = null; protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) => CollectionChanged?.Invoke(this, args); public object DeepClone() { var clone = new PropertyGroupCollection(); foreach (var group in this) { clone.Add((PropertyGroup)group.DeepClone()); } return clone; } } }