using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; using System.Threading; using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { /// /// Represents a Collection of objects /// [Serializable] [DataContract(IsReference = true)] public class PropertyCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable { private readonly object _addLocker = new object(); internal Action OnAdd; internal Func ValidateAdd { get; set; } internal PropertyCollection() : base(StringComparer.InvariantCultureIgnoreCase) { } /// /// Initializes a new instance of the class with a delegate responsible for validating the addition of instances. /// /// The validation callback. /// internal PropertyCollection(Func validationCallback) : this() { ValidateAdd = validationCallback; } public PropertyCollection(IEnumerable properties) : this() { Reset(properties); } /// /// Resets the collection to only contain the instances referenced in the parameter, whilst maintaining /// any validation delegates such as /// /// The properties. /// internal void Reset(IEnumerable properties) { Clear(); properties.ForEach(Add); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } protected override void SetItem(int index, Property item) { base.SetItem(index, item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); } protected override void RemoveItem(int index) { var removed = this[index]; base.RemoveItem(index); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); } protected override void InsertItem(int index, Property item) { base.InsertItem(index, item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } protected override void ClearItems() { base.ClearItems(); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } internal new void Add(Property item) { lock (_addLocker) { var key = GetKeyForItem(item); if (key != null) { var exists = this.Contains(key); if (exists) { //NOTE: Consider checking type before value is set: item.PropertyType.DataTypeId == property.PropertyType.DataTypeId //Transfer the existing value to the new property var property = this[key]; if (item.Id == 0 && property.Id != 0) { item.Id = property.Id; } if (item.Value == null && property.Value != null) { item.Value = property.Value; } SetItem(IndexOfKey(key), item); return; } } base.Add(item); OnAdd.IfNotNull(x => x.Invoke());//Could this not be replaced by a Mandate/Contract for ensuring item is not null OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } } /// /// Determines whether this collection contains a whose alias matches the specified PropertyType. /// /// Alias of the PropertyType. /// true if the collection contains the specified alias; otherwise, false. /// public new bool Contains(string propertyTypeAlias) { return base.Contains(propertyTypeAlias); } public int IndexOfKey(string key) { for (var i = 0; i < this.Count; i++) { if (this[i].Alias.InvariantEquals(key)) { return i; } } return -1; } protected override string GetKeyForItem(Property item) { return item.Alias; } /// /// Gets the element with the specified PropertyType. /// /// /// /// The element with the specified PropertyType. If an element with the specified PropertyType is not found, an exception is thrown. /// /// The PropertyType of the element to get. is null.An element with the specified key does not exist in the collection. internal Property this[PropertyType propertyType] { get { return this.FirstOrDefault(x => x.Alias.InvariantEquals(propertyType.Alias)); } } public event NotifyCollectionChangedEventHandler CollectionChanged; protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) { if (CollectionChanged != null) { CollectionChanged(this, args); } } /// /// Ensures that the collection contains Properties for the passed in PropertyTypes /// /// List of PropertyType protected internal void EnsurePropertyTypes(IEnumerable propertyTypes) { if (/*!this.Any() &&*/ propertyTypes != null) { foreach (var propertyType in propertyTypes) { Add(new Property(propertyType)); } } } /// /// Ensures that the collection is cleared from PropertyTypes not in the list of passed in PropertyTypes /// /// List of PropertyType protected internal void EnsureCleanPropertyTypes(IEnumerable propertyTypes) { if (propertyTypes != null) { //Remove PropertyTypes that doesn't exist in the list of new PropertyTypes var aliases = this.Select(p => p.Alias).Except(propertyTypes.Select(x => x.Alias)).ToList(); foreach (var alias in aliases) { Remove(alias); } //Add new PropertyTypes from the list of passed in PropertyTypes foreach (var propertyType in propertyTypes) { Add(new Property(propertyType)); } } } /// /// Create a deep clone of this property collection /// /// public object DeepClone() { var newList = new PropertyCollection(); foreach (var p in this) { newList.Add((Property)p.DeepClone()); } return newList; } } }