File: MS\Internal\Ink\ExtendedPropertyCollection.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
namespace System.Windows.Ink
{
    /// <summary>
    /// A collection of name/value pairs, called ExtendedProperties, can be stored
    /// in a collection to enable aggregate operations and assignment to Ink object
    /// model objects, such StrokeCollection and Stroke.
    /// </summary>
    internal sealed class ExtendedPropertyCollection //does not implement ICollection, we don't need it
    {
        /// <summary>
        /// Create a new empty ExtendedPropertyCollection
        /// </summary>
        internal ExtendedPropertyCollection()
        {
        }
 
        /// <summary>Overload of the Equals method which determines if two ExtendedPropertyCollection
        /// objects contain equivalent key/value pairs</summary>
        public override bool Equals(object o)
        {
            if (o == null || o.GetType() != GetType())
            {
                return false;
            }
 
            //
            // compare counts
            //
            ExtendedPropertyCollection that = (ExtendedPropertyCollection)o;
            if (that.Count != this.Count)
            {
                return false;
            }
 
            //
            // counts are equal, compare individual items
            // 
            //
            for (int x = 0; x < that.Count; x++)
            {
                bool cont = false;
                for (int i = 0; i < _extendedProperties.Count; i++)
                {
                    if (_extendedProperties[i].Equals(that[x]))
                    {
                        cont = true;
                        break;
                    }
                }
                if (!cont)
                {
                    return false;
                }
            }
            return true;
        }
 
        /// <summary>Overload of the equality operator which determines
        /// if two ExtendedPropertyCollections are equal</summary>
        public static bool operator ==(ExtendedPropertyCollection first, ExtendedPropertyCollection second)
        {
            // compare the GC ptrs for the obvious reference equality
            if (((object)first == null && (object)second == null) ||
                ((object)first == (object)second))
            {
                return true;
            }
            // otherwise, if one of the ptrs are null, but not the other then return false
            else if ((object)first == null || (object)second == null)
            {
                return false;
            }
            // finally use the full `blown value-style comparison against the collection contents
            else
            {
                return first.Equals(second);
            }
        }
 
        /// <summary>Overload of the not equals operator to determine if two
        /// ExtendedPropertyCollections have different key/value pairs</summary>
        public static bool operator!=(ExtendedPropertyCollection first, ExtendedPropertyCollection second)
        {
            return !(first == second);
        }
 
        /// <summary>
        /// GetHashCode
        /// </summary>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
 
        /// <summary>
        /// Check to see if the attribute is defined in the collection. 
        /// </summary>
        /// <param name="attributeId">Attribute identifier</param>
        /// <returns>True if attribute is set in the mask, false otherwise</returns>
        internal bool Contains(Guid attributeId)
        {
            for (int x = 0; x < _extendedProperties.Count; x++)
            {
                if (_extendedProperties[x].Id == attributeId)
                {
                    //
                    // a typical pattern is to first check if 
                    // ep.Contains(guid)
                    // before accessing:
                    // object o = ep[guid];
                    //
                    // I'm caching the index that contains returns so that we
                    // can look there first for the guid in the indexer
                    //
                    _optimisticIndex = x;
                    return true;
                }
            }
            return false;
        }
 
        /// <summary>
        /// Copies the ExtendedPropertyCollection
        /// </summary>
        /// <returns>Copy of the ExtendedPropertyCollection</returns>
        /// <remarks>Any reference types held in the collection will only be deep copied (e.g. Arrays).
        /// </remarks>
        internal ExtendedPropertyCollection Clone()
        {
            ExtendedPropertyCollection copied = new ExtendedPropertyCollection();
            for (int x = 0; x < _extendedProperties.Count; x++)
            {
                copied.Add(_extendedProperties[x].Clone());
            }
            return copied;
        }
 
        /// <summary>
        /// Add
        /// </summary>
        /// <param name="id">Id</param>
        /// <param name="value">value</param>
        internal void Add(Guid id, object value)
        {
            if (this.Contains(id))
            {
                throw new ArgumentException(SR.EPExists, "id");
            }
 
            ExtendedProperty extendedProperty = new ExtendedProperty(id, value);
 
            //this will raise change events
            this.Add(extendedProperty);
        }
 
 
        /// <summary>
        /// Remove
        /// </summary>
        /// <param name="id">id</param>
        internal void Remove(Guid id)
        {
            if (!Contains(id))
            {
                throw new ArgumentException(SR.EPGuidNotFound, "id");
            }
 
            ExtendedProperty propertyToRemove = GetExtendedPropertyById(id);
            System.Diagnostics.Debug.Assert(propertyToRemove != null);
 
            _extendedProperties.Remove(propertyToRemove);
 
            //
            // this value is bogus now
            //
            _optimisticIndex = -1;
 
            // fire notification event
            if (this.Changed != null)
            {
                ExtendedPropertiesChangedEventArgs eventArgs
                    = new ExtendedPropertiesChangedEventArgs(propertyToRemove, null);
                this.Changed(this, eventArgs);
            }
        }
 
        /// <value>
        ///     Retrieve the Guid array of ExtendedProperty Ids  in the collection.
        ///     <paramref>Guid[]</paramref> is of type <see cref="System.Int32"/>.
        ///     <seealso cref="System.Collections.ICollection.Count"/>
        /// </value>
        internal Guid[] GetGuidArray()
        {
            if (_extendedProperties.Count > 0)
            {
                Guid[] guids = new Guid[_extendedProperties.Count];
                for (int i = 0; i < _extendedProperties.Count; i++)
                {
                    guids[i] = this[i].Id;
                }
                return guids;
            }
            else
            {
                return Array.Empty<Guid>();
            }
        }
 
        /// <summary>
        /// Generic accessor for the ExtendedPropertyCollection. 
        /// </summary>
        /// <param name="attributeId">Attribue Id to find</param>
        /// <returns>Value for attribute specified by Id</returns>
        /// <exception cref="System.ArgumentException">Specified identifier was not found</exception>
        /// <remarks>
        /// Note that you can access extended properties via this indexer.
        /// </remarks>
        internal object this[Guid attributeId]
        {
            get
            {
                ExtendedProperty ep = GetExtendedPropertyById(attributeId);
                if (ep == null)
                {
                    throw new ArgumentException(SR.EPNotFound, "attributeId");
                }
                return ep.Value;
            }
            set
            {
                ArgumentNullException.ThrowIfNull(value);
                for (int i = 0; i < _extendedProperties.Count; i++)
                {
                    ExtendedProperty currentProperty = _extendedProperties[i];
 
                    if (currentProperty.Id == attributeId)
                    {
                        object oldValue = currentProperty.Value;
                        //this will raise events
                        currentProperty.Value = value;
 
                        //raise change if anyone is listening
                        if (this.Changed != null)
                        {
                            ExtendedPropertiesChangedEventArgs eventArgs
                                = new ExtendedPropertiesChangedEventArgs(
                                    new ExtendedProperty(currentProperty.Id, oldValue), //old prop
                                    currentProperty);                                   //new prop
 
                            this.Changed(this, eventArgs);
                        }
                        return;
                    }
                }
 
                //
                //  we didn't find the Id in the collection, we need to add it.
                //  this will raise change notifications
                //
                ExtendedProperty attributeToAdd = new ExtendedProperty(attributeId, value);
                this.Add(attributeToAdd);
            }
        }
 
        /// <summary>
        /// Generic accessor for the ExtendedPropertyCollection. 
        /// </summary>
        /// <param name="index">index into masking collection to retrieve</param>
        /// <returns>ExtendedProperty specified at index</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Index was not found</exception>
        /// <remarks>
        /// Note that you can access extended properties via this indexer.
        /// </remarks>
        internal ExtendedProperty this[int index]
        {
            get
            {
                return _extendedProperties[index];
            }
        }
 
        /// <value>
        ///     Retrieve the number of ExtendedProperty objects in the collection.
        ///     <paramref>Count</paramref> is of type <see cref="System.Int32"/>.
        ///     <seealso cref="System.Collections.ICollection.Count"/>
        /// </value>
        internal int Count
        {
            get
            {
                return _extendedProperties.Count;
            }
        }
 
        /// <summary>
        /// Event fired whenever a ExtendedProperty is modified in the collection
        /// </summary>
        internal event ExtendedPropertiesChangedEventHandler Changed;
 
 
        /// <summary>
        /// private Add, we need to consider making this public in order to implement the generic ICollection
        /// </summary>
        private void Add(ExtendedProperty extendedProperty)
        {
            System.Diagnostics.Debug.Assert(!this.Contains(extendedProperty.Id), "ExtendedProperty already belongs to the collection");
 
            _extendedProperties.Add(extendedProperty);
 
            // fire notification event
            if (this.Changed != null)
            {
                ExtendedPropertiesChangedEventArgs eventArgs
                    = new ExtendedPropertiesChangedEventArgs(null, extendedProperty);
                this.Changed(this, eventArgs);
            }
        }
 
        /// <summary>
        /// Private helper for getting an EP out of our internal collection
        /// </summary>
        /// <param name="id">id</param>
        private ExtendedProperty GetExtendedPropertyById(Guid id)
        {
            //
            // a typical pattern is to first check if 
            // ep.Contains(guid)
            // before accessing:
            // object o = ep[guid];
            //
            // The last call to .Contains sets this index
            //
            if (_optimisticIndex != -1 &&
                _optimisticIndex < _extendedProperties.Count &&
                _extendedProperties[_optimisticIndex].Id == id)
            {
                return _extendedProperties[_optimisticIndex];
            }
 
                //we didn't find the ep optimistically, perform linear lookup
            for (int i = 0; i < _extendedProperties.Count; i++)
            {
                if (_extendedProperties[i].Id == id)
                {
                    return _extendedProperties[i];
                }
            }
 
            return null;
        }
        
        // the set of ExtendedProperties stored in this collection
        private List<ExtendedProperty> _extendedProperties = new List<ExtendedProperty>();
 
        
        //used to optimize across Contains / Index calls
        private int _optimisticIndex = -1;
    }
}