File: Base\System\Windows\PropertyMetadata.cs
Project: wpf\src\WindowsBase.csproj (WindowsBase)
using MS.Internal;
using MS.Utility;
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Collections;
using System.Windows.Threading;  // for DispatcherObject
 
using MS.Internal.WindowsBase;
 
namespace System.Windows
{
    /// <summary>
    ///     Type-specific property metadata
    /// </summary>
    public class PropertyMetadata
    {
        /// <summary>
        ///     Type metadata construction
        /// </summary>
        public PropertyMetadata()
        {
        }
 
        /// <summary>
        ///     Type metadata construction
        /// </summary>
        /// <param name="defaultValue">Default value of property</param>
        public PropertyMetadata(object defaultValue)
        {
            DefaultValue = defaultValue;
        }
 
        /// <summary>
        ///     Type meta construction
        /// </summary>
        /// <param name="propertyChangedCallback">Called when the property has been changed</param>
        public PropertyMetadata(PropertyChangedCallback propertyChangedCallback)
        {
            PropertyChangedCallback = propertyChangedCallback;
        }
 
        /// <summary>
        ///     Type meta construction
        /// </summary>
        /// <param name="defaultValue">Default value of property</param>
        /// <param name="propertyChangedCallback">Called when the property has been changed</param>
        public PropertyMetadata(object defaultValue,
                                PropertyChangedCallback propertyChangedCallback)
        {
            DefaultValue = defaultValue;
            PropertyChangedCallback = propertyChangedCallback;
        }
 
        /// <summary>
        ///     Type meta construction
        /// </summary>
        /// <param name="defaultValue">Default value of property</param>
        /// <param name="propertyChangedCallback">Called when the property has been changed</param>
        /// <param name="coerceValueCallback">Called on update of value</param>
        public PropertyMetadata(object defaultValue,
                                PropertyChangedCallback propertyChangedCallback,
                                CoerceValueCallback coerceValueCallback)
        {
            DefaultValue = defaultValue;
            PropertyChangedCallback = propertyChangedCallback;
            CoerceValueCallback = coerceValueCallback;
        }
 
        // 
 
 
        /// <summary>
        ///     Default value of property
        /// </summary>
        public object DefaultValue
        {
            get
            {
                DefaultValueFactory defaultFactory = _defaultValue as DefaultValueFactory;
                if (defaultFactory == null)
                {
                    return _defaultValue;
                }
                else
                {
                    return defaultFactory.DefaultValue;
                }
            }
 
            set
            {
                if (Sealed)
                {
                    throw new InvalidOperationException(SR.Get(SRID.TypeMetadataCannotChangeAfterUse));
                }
 
                if (value == DependencyProperty.UnsetValue)
                {
                    throw new ArgumentException(SR.Get(SRID.DefaultValueMayNotBeUnset));
                }
 
                _defaultValue = value;
 
                SetModified(MetadataFlags.DefaultValueModifiedID);
            }
        }
 
        // 
 
 
        /// <summary>
        ///     Returns true if the default value is a DefaultValueFactory
        /// </summary>
        internal bool UsingDefaultValueFactory
        {
            get
            {
                return _defaultValue is DefaultValueFactory;
            }
        }
 
        // 
 
 
        /// <summary>
        /// GetDefaultValue returns the default value for a given owner and property.
        /// If the default value is a DefaultValueFactory it will instantiate and cache
        /// the default value on the object.  It must never return an unfrozen default
        /// value if the owner is a frozen Freezable.
        /// </summary>
        /// <param name="owner"></param>
        /// <param name="property"></param>
        /// <returns></returns>
        [FriendAccessAllowed] // Built into Base, also used by Framework.
        internal object GetDefaultValue(DependencyObject owner, DependencyProperty property)
        {
            Debug.Assert(owner != null && property != null,
                "Caller must provide owner and property or this method will throw in the event of a cache miss.");
 
            // If we are not using a DefaultValueFactory (common case)
            // just return _defaultValue
            DefaultValueFactory defaultFactory = _defaultValue as DefaultValueFactory;
            if (defaultFactory == null)
            {
                return _defaultValue;
            }
 
            // If the owner is Sealed it must not have a cached Freezable default value,
            // regardless of whether or not the owner is a Freezable.  The reason
            // for this is that a default created using the FreezableDefaultValueFactory
            // will attempt to set itself as a local value if it is changed.  Since the owner
            // is Sealed this will throw an exception.
            //
            // The solution to this if the owner is a Freezable is to toss out all cached
            // default values when we Seal.  If the owner is not a Freezable we'll promote
            // the value to locally cached.  Either way no Sealed DO can have a cached
            // default value, so we'll return the frozen default value instead.
            if (owner.IsSealed)
            {
                return defaultFactory.DefaultValue;
            }
 
            // See if we already have a valid default value that was
            // created by a prior call to GetDefaultValue.
            object result = GetCachedDefaultValue(owner, property);
 
            if (result != DependencyProperty.UnsetValue)
            {
                // When sealing a DO we toss out all the cached values (see DependencyObject.Seal()).
                // We technically only need to throw out cached values created via the
                // FreezableDefaultValueFactory, but it's more consistent this way.
                Debug.Assert(!owner.IsSealed,
                    "If the owner is Sealed we should not have a cached default value");
 
                return result;
            }
 
            // Otherwise we need to invoke the factory to create the DefaultValue
            // for this property.
            result = defaultFactory.CreateDefaultValue(owner, property);
 
            // Default value validation ensures that default values do not have
            // thread affinity. This is because a default value is typically 
            // stored in the shared property metadata and handed out to all
            // instances of the owning DependencyObject type.  
            //
            // DefaultValueFactory.CreateDefaultValue ensures that the default  
            // value has thread-affinity to the current thread.  We can thus 
            // skip that portion of the default value validation by calling
            // ValidateFactoryDefaultValue.
 
            Debug.Assert(!(result is DispatcherObject) || ((DispatcherObject)result).Dispatcher == owner.Dispatcher);
 
            property.ValidateFactoryDefaultValue(result);
 
            // Cache the created DefaultValue so that we can consistently hand
            // out the same default each time we are asked.
            SetCachedDefaultValue(owner, property, result);
 
            return result;
        }
 
        // Because the frugalmap is going to be stored in an uncommon field, it would get boxed
        // to avoid this boxing, skip the struct and go straight for the class contained by the
        // struct.  Given the simplicity of this scenario, we can get away with this.
        private object GetCachedDefaultValue(DependencyObject owner, DependencyProperty property)
        {
            FrugalMapBase map = _defaultValueFactoryCache.GetValue(owner);
 
            if (map == null)
            {
                return DependencyProperty.UnsetValue;
            }
 
            return map.Search(property.GlobalIndex);
        }
 
        private void SetCachedDefaultValue(DependencyObject owner, DependencyProperty property, object value)
        {
            FrugalMapBase map = _defaultValueFactoryCache.GetValue(owner);
 
            if (map == null)
            {
                map = new SingleObjectMap();
                _defaultValueFactoryCache.SetValue(owner, map);
            }
            else if (!(map is HashObjectMap))
            {
                FrugalMapBase newMap = new HashObjectMap();
                map.Promote(newMap);
                map = newMap;
                _defaultValueFactoryCache.SetValue(owner, map);
            }
 
            map.InsertEntry(property.GlobalIndex, value);
        }
 
        /// <summary>
        ///     This method causes the DefaultValue cache to be cleared ensuring
        ///     that CreateDefaultValue will be called next time this metadata
        ///     is asked to participate in the DefaultValue factory pattern.
        ///
        ///     This is internal so it can be accessed by subclasses of
        ///     DefaultValueFactory.
        /// </summary>
        internal void ClearCachedDefaultValue(DependencyObject owner, DependencyProperty property)
        {
            FrugalMapBase map = _defaultValueFactoryCache.GetValue(owner);
            if (map.Count == 1)
            {
                _defaultValueFactoryCache.ClearValue(owner);
            }
            else
            {
                map.RemoveEntry(property.GlobalIndex);
            }
        }
 
        internal static void PromoteAllCachedDefaultValues(DependencyObject owner)
        {
            FrugalMapBase map = _defaultValueFactoryCache.GetValue(owner);
 
            if (map != null)
            {
                // Iterate through all the items in the map (each representing a DP)
                // and promote them to locally-set.
                map.Iterate(null, _promotionCallback);
            }
        }
 
        /// <summary>
        /// Removes all cached default values on an object.  It iterates though
        /// each one and, if the cached default is a Freezable, removes its
        /// Changed event handlers. This is called by DependencyObject.Seal()
        /// for Freezable type owners.
        /// </summary>
        /// <param name="owner"></param>
        internal static void RemoveAllCachedDefaultValues(Freezable owner)
        {
            FrugalMapBase map = _defaultValueFactoryCache.GetValue(owner);
 
            if (map != null)
            {
                // Iterate through all the items in the map (each representing a DP)
                // and remove the promotion handlers
                map.Iterate(null, _removalCallback);
 
                // Now that they're all clear remove the map.
                _defaultValueFactoryCache.ClearValue(owner);
            }
        }
 
 
        /// <summary>
        /// Called once per iteration through the FrugalMap containing all cached default values
        /// for a given DependencyObject. This method removes the promotion handlers on each cached
        /// default and freezes it.
        /// </summary>
        /// <param name="list"></param>
        /// <param name="key">The DP's global index</param>
        /// <param name="value">The cached default</param>
        private static void DefaultValueCacheRemovalCallback(ArrayList list, int key, object value)
        {
            Freezable cachedDefault = value as Freezable;
 
            if (cachedDefault != null)
            {
                // Freeze fires the Changed event so we need to clear off the handlers before
                // calling it.  Otherwise the promoter would run and attempt to set the
                // cached default as a local value.
                cachedDefault.ClearContextAndHandlers();
                cachedDefault.Freeze();
            }
        }
 
 
 
        /// <summary>
        /// Called once per iteration through the FrugalMap containing all cached default values
        /// for a given DependencyObject. This method promotes each of the defaults to locally-set.
        /// </summary>
        /// <param name="list"></param>
        /// <param name="key">The DP's global index</param>
        /// <param name="value">The cached default</param>
        private static void DefaultValueCachePromotionCallback(ArrayList list, int key, object value)
        {
            Freezable cachedDefault = value as Freezable;
 
            if (cachedDefault != null)
            {
                // The only way to promote a cached default is to fire its Changed event.
                cachedDefault.FireChanged();
            }
        }
 
 
        /// <summary>
        ///     Whether the DefaultValue was explictly set - needed to know if the
        /// value should be used in Register.
        /// </summary>
        internal bool DefaultValueWasSet()
        {
            return IsModified(MetadataFlags.DefaultValueModifiedID);
        }
 
        /// <summary>
        ///     Property changed callback
        /// </summary>
        public PropertyChangedCallback PropertyChangedCallback
        {
            get { return _propertyChangedCallback; }
            set
            {
                if (Sealed)
                {
                    throw new InvalidOperationException(SR.Get(SRID.TypeMetadataCannotChangeAfterUse));
                }
 
                _propertyChangedCallback = value;
            }
        }
 
        /// <summary>
        ///     Specialized callback invoked upon a call to UpdateValue
        /// </summary>
        /// <remarks>
        ///     Used for "coercing" effective property value without actually subclassing
        /// </remarks>
        public CoerceValueCallback CoerceValueCallback
        {
            get { return _coerceValueCallback; }
            set
            {
                if (Sealed)
                {
                    throw new InvalidOperationException(SR.Get(SRID.TypeMetadataCannotChangeAfterUse));
                }
 
                _coerceValueCallback = value;
            }
        }
 
 
        /// <summary>
        ///     Specialized callback for remote storage of value for read-only properties
        /// </summary>
        /// <remarks>
        ///     This is used exclusively by FrameworkElement.ActualWidth and ActualHeight to save 48 bytes
        ///     of state per FrameworkElement.
        /// </remarks>
        [FriendAccessAllowed] // Built into Base, also used by Framework.
        internal virtual GetReadOnlyValueCallback GetReadOnlyValueCallback
        {
            get
            {
                return null;
            }
        }
 
        /// <summary>
        ///     Specialized callback invoked for each property when a Freezable
        ///     object is frozen.
        /// </summary>
        /// <remarks>
        ///     Used to provide specialized behavior when freezing an object
        ///     that a property has been set on.  This callback can be used to
        ///     decide whether to do a "deep" freeze, a "shallow" freeze, to
        ///     fail the freeze attempt, etc.
        /// </remarks>
        [FriendAccessAllowed] // Currently used by Storyboard in PresentationFramework.
        internal FreezeValueCallback FreezeValueCallback
        {
            get
            {
                if(_freezeValueCallback != null)
                {
                    return _freezeValueCallback;
                }
                else
                {
                    return _defaultFreezeValueCallback;
                }
            }
 
            set
            {
                if (Sealed)
                {
                    throw new InvalidOperationException(SR.Get(SRID.TypeMetadataCannotChangeAfterUse));
                }
 
                _freezeValueCallback = value;
            }
        }
 
        private static bool DefaultFreezeValueCallback(
            DependencyObject d,
            DependencyProperty dp,
            EntryIndex entryIndex,
            PropertyMetadata metadata,
            bool isChecking)
        {
            // The expression check only needs to be done when isChecking is true
            // because if we return false here the Freeze() call will fail.
            if (isChecking)
            {
                if (d.HasExpression(entryIndex, dp))
                {
                    if (TraceFreezable.IsEnabled)
                    {
                        TraceFreezable.Trace(
                            TraceEventType.Warning,
                            TraceFreezable.UnableToFreezeExpression,
                            d,
                            dp,
                            dp.OwnerType);
                    }
 
                    return false;
                }
            }
 
            if (!dp.IsValueType)
            {
                object value =
                    d.GetValueEntry(
                        entryIndex,
                        dp,
                        metadata,
                        RequestFlags.FullyResolved).Value;
 
                if (value != null)
                {
                    Freezable valueAsFreezable = value as Freezable;
 
                    if (valueAsFreezable != null)
                    {
                        if (!valueAsFreezable.Freeze(isChecking))
                        {
                            if (TraceFreezable.IsEnabled)
                            {
                                TraceFreezable.Trace(
                                    TraceEventType.Warning,
                                    TraceFreezable.UnableToFreezeFreezableSubProperty,
                                    d,
                                    dp,
                                    dp.OwnerType);
                            }
 
                            return false;
                        }
                    }
                    else  // not a Freezable
                    {
                        DispatcherObject valueAsDispatcherObject = value as DispatcherObject;
 
                        if (valueAsDispatcherObject != null)
                        {
                            if (valueAsDispatcherObject.Dispatcher == null)
                            {
                                // The property is a free-threaded DispatcherObject; since it's
                                // already free-threaded it doesn't prevent this Freezable from
                                // becoming free-threaded too.
                                // It is up to the creator of this type to ensure that the
                                // DispatcherObject is actually immutable
                            }
                            else
                            {
                                // The value of this property derives from DispatcherObject and
                                // has thread affinity; return false.
 
                                if (TraceFreezable.IsEnabled)
                                {
                                    TraceFreezable.Trace(
                                        TraceEventType.Warning,
                                        TraceFreezable.UnableToFreezeDispatcherObjectWithThreadAffinity,
                                        d,
                                        dp,
                                        dp.OwnerType,
                                        valueAsDispatcherObject );
                                }
 
                                return false;
                            }
                        }
 
                        // The property isn't a DispatcherObject.  It may be immutable (such as a string)
                        // or the user may have made it thread-safe.  It's up to the creator of the type to
                        // do the right thing; we return true as an extensibility point.
                    }
                }
            }
 
            return true;
        }
 
        private static FreezeValueCallback _defaultFreezeValueCallback = DefaultFreezeValueCallback;
 
        /// <summary>
        ///     Creates a new instance of this property metadata.  This method is used
        ///     when metadata needs to be cloned.  After CreateInstance is called the
        ///     framework will call Merge to merge metadata into the new instance.
        ///     Deriving classes must override this and return a new instance of
        ///     themselves.
        /// </summary>
        internal virtual PropertyMetadata CreateInstance() {
            return new PropertyMetadata();
        }
 
        //
        // Returns a copy of this property metadata by calling CreateInstance
        // and then Merge
        //
        internal PropertyMetadata Copy(DependencyProperty dp) {
            PropertyMetadata newMetadata = CreateInstance();
            newMetadata.InvokeMerge(this, dp);
            return newMetadata;
        }
 
        /// <summary>
        ///     Merge set source state into this
        /// </summary>
        /// <remarks>
        ///     Used when overriding metadata
        /// </remarks>
        /// <param name="baseMetadata">Base metadata to merge</param>
        /// <param name="dp">DependencyProperty that this metadata is being applied to</param>
        protected virtual void Merge(PropertyMetadata baseMetadata, DependencyProperty dp)
        {
            if (baseMetadata == null)
            {
                throw new ArgumentNullException("baseMetadata");
            }
 
            if (Sealed)
            {
                throw new InvalidOperationException(SR.Get(SRID.TypeMetadataCannotChangeAfterUse));
            }
 
            // Merge source metadata into this
 
            // Take source default if this default was never set
            if (!IsModified(MetadataFlags.DefaultValueModifiedID))
            {
                _defaultValue = baseMetadata.DefaultValue;
            }
 
            if (baseMetadata.PropertyChangedCallback != null)
            {
                // All delegates are MulticastDelegate.  Non-multicast "Delegate"
                //  was designed and is documented in MSDN.  But for all practical
                //  purposes, it was actually cut before v1.0 of the CLR shipped.
 
                // Build the handler list such that handlers added
                // via OverrideMetadata are called last (base invocation first)
                Delegate[] handlers = baseMetadata.PropertyChangedCallback.GetInvocationList();
                if (handlers.Length > 0)
                {
                    PropertyChangedCallback headHandler = (PropertyChangedCallback)handlers[0];
                    for (int i = 1; i < handlers.Length; i++)
                    {
                        headHandler += (PropertyChangedCallback)handlers[i];
                    }
                    headHandler += _propertyChangedCallback;
                    _propertyChangedCallback = headHandler;
                }
            }
 
// 
 
            if (_coerceValueCallback == null)
            {
                _coerceValueCallback = baseMetadata.CoerceValueCallback;
            }
 
            // FreezeValueCallback not combined
            if (_freezeValueCallback == null)
            {
                _freezeValueCallback = baseMetadata.FreezeValueCallback;
            }
        }
 
        internal void InvokeMerge(PropertyMetadata baseMetadata, DependencyProperty dp)
        {
            Merge(baseMetadata, dp);
        }
 
 
        /// <summary>
        ///     Notification that this metadata has been applied to a property
        ///     and the metadata is being sealed
        /// </summary>
        /// <remarks>
        ///     Normally, any mutability of the data structure should be marked
        ///     as immutable at this point
        /// </remarks>
        /// <param name="dp">DependencyProperty</param>
        /// <param name="targetType">Type associating metadata (null if default metadata)</param>
        protected virtual void OnApply(DependencyProperty dp, Type targetType)
        {
        }
 
 
        /// <summary>
        ///     Determines if the metadata has been applied to a property resulting
        ///     in the sealing (immutability) of the instance
        /// </summary>
        protected bool IsSealed
        {
            get { return Sealed; }
        }
 
 
        internal void Seal(DependencyProperty dp, Type targetType)
        {
            // CALLBACK
            OnApply(dp, targetType);
 
            Sealed = true;
        }
 
        internal bool IsDefaultValueModified { get { return IsModified(MetadataFlags.DefaultValueModifiedID); } }
 
        internal bool IsInherited
        {
            get { return (MetadataFlags.Inherited & _flags) != 0;; }
            set
            {
                if (value)
                {
                    _flags |= MetadataFlags.Inherited;
                }
                else
                {
                    _flags &= (~MetadataFlags.Inherited);
                }
            }
        }
 
        private object _defaultValue;
        private PropertyChangedCallback _propertyChangedCallback;
        private CoerceValueCallback _coerceValueCallback;
        private FreezeValueCallback _freezeValueCallback;
 
        // 
 
 
 
 
        [FriendAccessAllowed] // Built into Base, also used by Core and Framework.
        internal enum MetadataFlags : uint
        {
            DefaultValueModifiedID                       = 0x00000001,
            SealedID                                     = 0x00000002,
            // Unused                                    = 0x00000004,
            // Unused                                    = 0x00000008,
            Inherited                                    = 0x00000010,
 
            UI_IsAnimationProhibitedID                   = 0x00000020, // True if peer refers to an owner's animation peer property; False if Peer refers to the animation peer's owner property
 
            FW_AffectsMeasureID                          = 0x00000040,
            FW_AffectsArrangeID                          = 0x00000080,
            FW_AffectsParentMeasureID                    = 0x00000100,
            FW_AffectsParentArrangeID                    = 0x00000200,
            FW_AffectsRenderID                           = 0x00000400,
            FW_OverridesInheritanceBehaviorID            = 0x00000800,
            FW_IsNotDataBindableID                       = 0x00001000,
            FW_BindsTwoWayByDefaultID                    = 0x00002000,
            FW_ShouldBeJournaledID                       = 0x00004000,
            FW_SubPropertiesDoNotAffectRenderID          = 0x00008000,
            FW_SubPropertiesDoNotAffectRenderModifiedID  = 0x00010000,
            // Unused                                    = 0x00020000,
            // Unused                                    = 0x00040000,
            // Unused                                    = 0x00080000,
            FW_InheritsModifiedID                        = 0x00100000,
            FW_OverridesInheritanceBehaviorModifiedID    = 0x00200000,
            // Unused                                    = 0x00400000,
            // Unused                                    = 0x00800000,
            FW_ShouldBeJournaledModifiedID               = 0x01000000,
            FW_UpdatesSourceOnLostFocusByDefaultID       = 0x02000000,
            FW_DefaultUpdateSourceTriggerModifiedID      = 0x04000000,
            FW_ReadOnlyID                                = 0x08000000,
            // Unused                                    = 0x10000000,
            // Unused                                    = 0x20000000,
            FW_DefaultUpdateSourceTriggerEnumBit1        = 0x40000000, // Must match constants used in FrameworkPropertyMetadata
            FW_DefaultUpdateSourceTriggerEnumBit2        = 0x80000000, // Must match constants used in FrameworkPropertyMetadata
        }
 
 
        // PropertyMetadata, UIPropertyMetadata, and FrameworkPropertyMetadata.
        [FriendAccessAllowed] // Built into Base, also used by Core and Framework.
        internal MetadataFlags _flags;
 
        private void SetModified(MetadataFlags id) { _flags |= id; }
        private bool IsModified(MetadataFlags id) { return (id & _flags) != 0; }
 
        /// <summary>
        ///     Write a flag value
        /// </summary>
        [FriendAccessAllowed] // Built into Base, also used by Core and Framework.
        internal void WriteFlag(MetadataFlags id, bool value)
        {
            if (value)
            {
                _flags |= id;
            }
            else
            {
                _flags &= (~id);
            }
        }
 
        /// <summary>
        ///     Read a flag value
        /// </summary>
        [FriendAccessAllowed] // Built into Base, also used by Core and Framework.
        internal bool ReadFlag(MetadataFlags id) { return (id & _flags) != 0; }
 
        internal bool Sealed
        {
            [FriendAccessAllowed] // Built into Base, also used by Core.
            get { return ReadFlag(MetadataFlags.SealedID); }
            set { WriteFlag(MetadataFlags.SealedID, value); }
        }
 
        // We use this uncommon field to stash values created by our default value factory
        // in the owner's _localStore.
        private static readonly UncommonField<FrugalMapBase> _defaultValueFactoryCache = new UncommonField<FrugalMapBase>();
        private static FrugalMapIterationCallback _removalCallback = new FrugalMapIterationCallback(DefaultValueCacheRemovalCallback);
        private static FrugalMapIterationCallback _promotionCallback = new FrugalMapIterationCallback(DefaultValueCachePromotionCallback);
    }
 
    /// <summary>
    ///     GetReadOnlyValueCallback -- a very specialized callback that allows storage for read-only properties
    ///     to be managed outside of the effective value store on a DO.  This optimization is restricted to read-only
    ///     properties because read-only properties can only have a value explicitly set by the keeper of the key, so
    ///     it eliminates the possibility of a self-managed store missing modifiers such as expressions, coercion,
    ///     and animation.
    /// </summary>
    [FriendAccessAllowed] // Built into Base, also used by Framework.
    internal delegate object GetReadOnlyValueCallback(DependencyObject d, out BaseValueSourceInternal source);
}