File: System\Windows\Markup\Baml2006\WpfXamlType.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// 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.
 
using System.Xaml;
using System.Xaml.Schema;
using System.Collections;
using System.Reflection;
using System.Collections.Concurrent;
 
namespace System.Windows.Baml2006
{
    // XamlType for New types defined in the BAML stream.
    //
    internal class WpfXamlType : XamlType
    {
        [Flags]
        private enum BoolTypeBits
        {
            BamlScenerio = 0x0001,
            V3Rules     = 0x0002
        }
 
        const int ConcurrencyLevel = 1;
        // ConcurrentDictionary's capacity should not be divisible by a small prime.
        // ConcurrentDictionary grows by doing 2 * capacity + 1 and finding the first that isn't 
        //   divisible by 2,3,5,7.  Anything less than 11 would be inefficient in growing
        const int Capacity = 11;
 
        // In the "reading from BAML" senario we don't sperate Attachable from non-attachable.
        // BAML is pre-approved by the compiler so we don't have to worry about incorrect usage
        // BAML has higher performance with only one cache.  One less dictionary lookup.
        // But text can ask for <Brush StackPanel.Background="Red">  and property looks like
        // an attachable property and the look-up needs to fail. [in text].
        private ConcurrentDictionary<string, XamlMember> _attachableMembers;
        private ConcurrentDictionary<string, XamlMember> _members;
 
        // We share this with WpfKnownType.  Any changes to the bitfield needs to be propogated to WpfKnownType
        protected byte _bitField;
        private bool IsBamlScenario
        {
            get { return GetFlag(ref _bitField, (byte)BoolTypeBits.BamlScenerio); }
            set { SetFlag(ref _bitField, (byte)BoolTypeBits.BamlScenerio, value); }
        }
        private bool UseV3Rules
        {
            get { return GetFlag(ref _bitField, (byte)BoolTypeBits.V3Rules); }
            set { SetFlag(ref _bitField, (byte)BoolTypeBits.V3Rules, value); }
        }
 
        protected ConcurrentDictionary<string, XamlMember> Members
        {
            get
            {
                if (_members == null)
                {
                    _members = new ConcurrentDictionary<string, XamlMember>(ConcurrencyLevel, Capacity);
                }
                return _members;
            }
        }
 
        protected ConcurrentDictionary<string, XamlMember> AttachableMembers
        {
            get
            {
                if (_attachableMembers == null)
                {
                    _attachableMembers = new ConcurrentDictionary<string, XamlMember>(ConcurrencyLevel, Capacity);
                }
                return _attachableMembers;
            }
        }
 
        public WpfXamlType(Type type, XamlSchemaContext schema, bool isBamlScenario, bool useV3Rules)
            : base(type, schema)
        {
            IsBamlScenario = isBamlScenario;
            UseV3Rules = useV3Rules;
        }
 
        protected override XamlMember LookupContentProperty()
        {
            XamlMember result = base.LookupContentProperty();
            WpfXamlMember wpfMember = result as WpfXamlMember;
            if (wpfMember != null)
            {
                result = wpfMember.AsContentProperty;
            }
            return result;
        }
 
        protected override bool LookupIsNameScope()
        {
            if (UnderlyingType == typeof(ResourceDictionary))
            {
                return false;
            }
            else if (typeof(ResourceDictionary).IsAssignableFrom(UnderlyingType))
            {
                InterfaceMapping map = UnderlyingType.GetInterfaceMap(typeof(System.Windows.Markup.INameScope));
                foreach (MethodInfo method in map.TargetMethods)
                {
                    if (method.Name.Contains("RegisterName"))
                    {
                        return method.DeclaringType != typeof(ResourceDictionary);
                    }
                }
                return false;
            }
            else
            {
                return base.LookupIsNameScope();
            }
        }
 
        private XamlMember FindMember(string name, bool isAttached, bool skipReadOnlyCheck)
        {
            // Try looking for a known member
            XamlMember member = FindKnownMember(name, isAttached);
 
            // Return the known member if we have one
            if (member != null)
            {
                return member;
            }
 
            // Look for members backed by DPs
            member = FindDependencyPropertyBackedProperty(name, isAttached, skipReadOnlyCheck);
            if (member != null)
            {
                return member;
            }
 
            // Look for members backed by RoutedEvents
            member = FindRoutedEventBackedProperty(name, isAttached, skipReadOnlyCheck);
            if (member != null)
            {
                return member;
            }
 
            // Ask the base class (XamlType) to find the member
            // We make this call in case a user overrides or news a member
            if (isAttached)
            {
                member = base.LookupAttachableMember(name);
            }
            else
            {
                member = base.LookupMember(name, skipReadOnlyCheck);
            }
 
            // If the base class finds one and it's declared type is a known type,
            // try looking for a known property.
            WpfKnownType wpfKnownType;
            if (member != null && (wpfKnownType = member.DeclaringType as WpfKnownType) != null)
            {
                XamlMember knownMember = FindKnownMember(wpfKnownType, name, isAttached);
                if (knownMember != null)
                {
                    return knownMember;
                }
            }
            return member;
        }
 
        protected override XamlMember LookupMember(string name, bool skipReadOnlyCheck)
        {
            return FindMember(name, false /* isAttached */, skipReadOnlyCheck);
        }
 
        protected override XamlMember LookupAttachableMember(string name)
        {
            return FindMember(name, true /* isAttached */, false /* skipReadOnlyCheck doens't matter for Attached */);
        }
 
        protected override IEnumerable<XamlMember> LookupAllMembers()
        {
            List<XamlMember> members = new List<XamlMember>();
            var reflectedMembers = base.LookupAllMembers();
 
            foreach (var reflectedMember in reflectedMembers)
            {
                var member = reflectedMember;
                if (!(member is WpfXamlMember))
                {
                    member = GetMember(member.Name);
                }
                members.Add(member);
            }
 
            return members;
        }
 
        // This will first check the cache on the type
        // Then will look for a knownMember if the type is a WpfKnownType
        // Then we look a DependencyProperty for the member
        //      If we do find one, double check to see if there is a known member that matches the DP
        //          This only happens on non-known types that derive from known types
        // Otherwise, return null
        private XamlMember FindKnownMember(string name, bool isAttachable)
        {
            XamlType type = this;
            // Only support looking up KnownMembers on KnownTypes (otherwise we could miss a new/override member)
            if (this is WpfKnownType)
            {
                do
                {
                    WpfXamlType wpfXamlType = type as WpfXamlType;
                    XamlMember xamlMember = FindKnownMember(wpfXamlType, name, isAttachable);
                    if (xamlMember != null)
                    {
                        return xamlMember;
                    }
 
                    type = type.BaseType;
                }
                while (type != null);
            }
            return null;
        }
 
        private XamlMember FindRoutedEventBackedProperty(string name, bool isAttachable, bool skipReadOnlyCheck)
        {
            RoutedEvent re = EventManager.GetRoutedEventFromName(
                name, UnderlyingType);
            XamlMember xamlMember = null;
            if (re != null)
            {
                // Try looking for a known member first instead of creating a new WpfXamlMember
                WpfXamlType wpfXamlType = null;
                if (IsBamlScenario)
                {
                    wpfXamlType = System.Windows.Markup.XamlReader.BamlSharedSchemaContext.GetXamlType(re.OwnerType) as WpfXamlType;
                }
                else
                {
                    wpfXamlType = System.Windows.Markup.XamlReader.GetWpfSchemaContext().GetXamlType(re.OwnerType) as WpfXamlType;
                }
 
                if (wpfXamlType != null)
                {
                    xamlMember = FindKnownMember(wpfXamlType, name, isAttachable);
                }
 
                if (IsBamlScenario)
                {
                    xamlMember = new WpfXamlMember(re, isAttachable);
                }
                else
                {
                    if (isAttachable)
                    {
                        xamlMember = GetAttachedRoutedEvent(name, re);
                        if (xamlMember == null)
                        {
                            xamlMember = GetRoutedEvent(name, re, skipReadOnlyCheck);
                        }
 
                        if (xamlMember == null)
                        {
                            xamlMember = new WpfXamlMember(re, true);
                        }
                    }
                    else
                    {
                        xamlMember = GetRoutedEvent(name, re, skipReadOnlyCheck);
                        if (xamlMember == null)
                        {
                            xamlMember = GetAttachedRoutedEvent(name, re);
                        }
 
                        if (xamlMember == null)
                        {
                            xamlMember = new WpfXamlMember(re, false);
                        }
                    }
                }
                if (Members.TryAdd(name, xamlMember))
                {
                    return xamlMember;
                }
                else
                {
                    return Members[name];
                }
            }
            return xamlMember;
        }
 
        private XamlMember FindDependencyPropertyBackedProperty(string name, bool isAttachable, bool skipReadOnlyCheck)
        {
            XamlMember xamlMember = null;
 
            // If it's a dependency property, return a wrapping XamlMember
            DependencyProperty property;
            if ((property = DependencyProperty.FromName(name, this.UnderlyingType)) != null)
            {
                // Try looking for a known member first instead of creating a new WpfXamlMember
                WpfXamlType wpfXamlType = null;
                if (IsBamlScenario)
                {
                    wpfXamlType = System.Windows.Markup.XamlReader.BamlSharedSchemaContext.GetXamlType(property.OwnerType) as WpfXamlType;
                }
                else
                {
                    wpfXamlType = System.Windows.Markup.XamlReader.GetWpfSchemaContext().GetXamlType(property.OwnerType) as WpfXamlType;
                }
 
                if (wpfXamlType != null)
                {
                    xamlMember = FindKnownMember(wpfXamlType, name, isAttachable);
                }
 
                if (xamlMember == null)
                {
                    if (IsBamlScenario)
                    {
                        // In Baml Scenarios, we don't want to lookup the MemberInfo since we always know the 
                        // type converter and we don't allows DeferringLoader (since the MarkupCompiler doesn't support it)
                        xamlMember = new WpfXamlMember(property, isAttachable);
                    }
                    else
                    {
                        // Try to find the MemberInfo so we can use that directly.  There's no direct way to do this
                        // with XamlType so we'll just get the XamlMember and get the underlying member
                        if (isAttachable)
                        {
                            xamlMember = GetAttachedDependencyProperty(name, property);
                            if (xamlMember == null)
                            {
                                return null;
                            }
                        }
                        else
                        {
                            xamlMember = GetRegularDependencyProperty(name, property, skipReadOnlyCheck);
                            if (xamlMember == null)
                            {
                                xamlMember = GetAttachedDependencyProperty(name, property);
                            }
 
                            if (xamlMember == null)
                            {
                                xamlMember = new WpfXamlMember(property, false);
                            }
                        }
                    }
                    return CacheAndReturnXamlMember(xamlMember);
                }
            }
            return xamlMember;
        }
 
        private XamlMember CacheAndReturnXamlMember(XamlMember xamlMember)
        {
            // If you are read from BAML then store everything in Members.
            if (!xamlMember.IsAttachable || IsBamlScenario)
            {
                if (Members.TryAdd(xamlMember.Name, xamlMember))
                {
                    return xamlMember;
                }
                else
                {
                    return Members[xamlMember.Name];
                }
            }
            else
            {
                if (AttachableMembers.TryAdd(xamlMember.Name, xamlMember))
                {
                    return xamlMember;
                }
                else
                {
                    return AttachableMembers[xamlMember.Name];
                }
            }
        }
 
        private XamlMember GetAttachedRoutedEvent(string name, RoutedEvent re)
        {
            XamlMember memberFromBase = base.LookupAttachableMember(name);
            if (memberFromBase != null)
            {
                return new WpfXamlMember(re, (MethodInfo)memberFromBase.UnderlyingMember, SchemaContext, UseV3Rules);
            }
            return null;
        }
 
        private XamlMember GetRoutedEvent(string name, RoutedEvent re, bool skipReadOnlyCheck)
        {
            XamlMember memberFromBase = base.LookupMember(name, skipReadOnlyCheck);
            if (memberFromBase != null)
            {
                return new WpfXamlMember(re, (EventInfo)memberFromBase.UnderlyingMember, SchemaContext, UseV3Rules);
            }
            return null;
        }
 
        private XamlMember GetAttachedDependencyProperty(string name, DependencyProperty property)
        {
            XamlMember memberFromBase = base.LookupAttachableMember(name);
            if (memberFromBase != null)
            {
                return new WpfXamlMember(property,
                    memberFromBase.Invoker.UnderlyingGetter,
                    memberFromBase.Invoker.UnderlyingSetter,
                    SchemaContext, UseV3Rules);
            }
            return null;
        }
 
        private XamlMember GetRegularDependencyProperty(string name, DependencyProperty property, bool skipReadOnlyCheck)
        {
            XamlMember memberFromBase = base.LookupMember(name, skipReadOnlyCheck);
            if (memberFromBase != null)
            {
                PropertyInfo propertyInfo = memberFromBase.UnderlyingMember as PropertyInfo;
                if (propertyInfo != null)
                {
                    return new WpfXamlMember(property, propertyInfo, SchemaContext, UseV3Rules);
                }
                else
                {
                    throw new NotImplementedException();
                }
            }
 
            return null;
        }
 
        // First try looking at the cache
        // Then look to see if we have a known property on the type
        private static XamlMember FindKnownMember(WpfXamlType wpfXamlType, string name, bool isAttachable)
        {
            XamlMember xamlMember = null;
 
            // Look in the cache first
            if (!isAttachable || wpfXamlType.IsBamlScenario)
            {
                if (wpfXamlType._members != null && wpfXamlType.Members.TryGetValue(name, out xamlMember))
                {
                    return xamlMember;
                }
            }
            else
            {
                if (wpfXamlType._attachableMembers != null && wpfXamlType.AttachableMembers.TryGetValue(name, out xamlMember))
                {
                    return xamlMember;
                }
            }
 
            WpfKnownType knownType = wpfXamlType as WpfKnownType;
            // Only look for known properties on a known type
            if (knownType != null)
            {
                // if it is a Baml Senario BAML doesn't really care if it was attachable or not
                // so look for the property in AttachableMembers also if it wasn't found in Members.
                if (!isAttachable || wpfXamlType.IsBamlScenario)
                {
                    xamlMember = System.Windows.Markup.XamlReader.BamlSharedSchemaContext.CreateKnownMember(wpfXamlType.Name, name);
                }
                if (isAttachable || (xamlMember == null && wpfXamlType.IsBamlScenario))
                {
                    xamlMember = System.Windows.Markup.XamlReader.BamlSharedSchemaContext.CreateKnownAttachableMember(wpfXamlType.Name, name);
                }
 
                if (xamlMember != null)
                {
                    return knownType.CacheAndReturnXamlMember(xamlMember);
                }
            }
            return null;
        }
 
        protected override XamlCollectionKind LookupCollectionKind()
        {
            if (UseV3Rules)
            {
                if (UnderlyingType.IsArray)
                {
                    return XamlCollectionKind.Array;
                }
                if (typeof(IDictionary).IsAssignableFrom(UnderlyingType))
                {
                    return XamlCollectionKind.Dictionary;
                }
                if (typeof(IList).IsAssignableFrom(UnderlyingType))
                {
                    return XamlCollectionKind.Collection;
                }
                // Several types in V3 implemented IAddChildInternal which allowed them to be collections
                if (typeof(System.Windows.Documents.DocumentReferenceCollection).IsAssignableFrom(UnderlyingType) || 
                    typeof(System.Windows.Documents.PageContentCollection).IsAssignableFrom(UnderlyingType))
                {
                    return XamlCollectionKind.Collection;
                } 
                // Doing a type comparison against XmlNamespaceMappingCollection will load System.Xml. We get around
                // this by only doing the comparison if it's an ICollection<XmlNamespaceMapping>
                if (typeof(ICollection<System.Windows.Data.XmlNamespaceMapping>).IsAssignableFrom(UnderlyingType) 
                    && IsXmlNamespaceMappingCollection)
                {
                    return XamlCollectionKind.Collection;
                }
                return XamlCollectionKind.None;
            }
            else
            {
                return base.LookupCollectionKind();
            }
        }
 
        // Having this directly in LookupCollectionKind forces System.Xml to load.
        private bool IsXmlNamespaceMappingCollection
        {
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
            get
            {
                return typeof(System.Windows.Data.XmlNamespaceMappingCollection).IsAssignableFrom(UnderlyingType);
            }
        }
        internal XamlMember FindBaseXamlMember(string name, bool isAttachable)
        {
            if (isAttachable)
            {
                return base.LookupAttachableMember(name);
            }
            else
            {
                return base.LookupMember(name, true);
            }            
        }
 
        internal static bool GetFlag(ref byte bitField, byte typeBit)
        {
            return (bitField & typeBit) != 0;
        }
 
        internal static void SetFlag(ref byte bitField, byte typeBit, bool value)
        {
            if (value)
            {
                bitField = (byte)(bitField | typeBit);
            }
            else
            {
                bitField = (byte)(bitField & ~typeBit);
            }
        }
    }
}