File: compmod\system\componentmodel\MemberDescriptor.cs
Project: ndp\fx\src\System.csproj (System)
//------------------------------------------------------------------------------
// <copyright file="MemberDescriptor.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
/*
 */
namespace System.ComponentModel {
 
    using Microsoft.Win32;
    using System;
    using System.Collections;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Reflection;
    using System.Security.Permissions;
 
    /// <devdoc>
    ///    <para>
    ///       Declares an array of attributes for a member and defines
    ///       the properties and methods that give you access to the attributes in the array.
    ///       All attributes must derive from <see cref='System.Attribute'/>.
    ///    </para>
    /// </devdoc>
    [HostProtection(SharedState = true)]
    [System.Runtime.InteropServices.ComVisible(true)]
    public abstract class MemberDescriptor {
        private string name;
        private string displayName;
        private int nameHash;
        private AttributeCollection attributeCollection;
        private Attribute[] attributes;
        private Attribute[] originalAttributes;
        private bool attributesFiltered = false;
        private bool attributesFilled = false;
        private int metadataVersion;
        private string category;
        private string description;
        private object lockCookie = new object();
 
 
        /// <devdoc>
        ///    <para>
        ///       Initializes a new instance of the <see cref='System.ComponentModel.MemberDescriptor'/> class with the specified <paramref name="name
        ///       "/> and no
        ///       attributes.
        ///    </para>
        /// </devdoc>
        protected MemberDescriptor(string name) : this(name, null) {
        }
 
        /// <devdoc>
        ///    <para>
        ///       Initializes a new instance of the <see cref='System.ComponentModel.MemberDescriptor'/> class with the specified <paramref name="name"/>
        ///       and <paramref name="attributes "/>
        ///       array.
        ///    </para>
        /// </devdoc>
        protected MemberDescriptor(string name, Attribute[] attributes) {
            try {
                if (name == null || name.Length == 0) {
                    throw new ArgumentException(SR.GetString(SR.InvalidMemberName));
                }
                this.name = name;
                this.displayName = name;
                this.nameHash = name.GetHashCode();
                if (attributes != null) {
                    this.attributes = attributes;
                    attributesFiltered = false;
                }
 
                this.originalAttributes = this.attributes;
            }
            catch (Exception t) {
                Debug.Fail(t.ToString());
                throw t;
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Initializes a new instance of the <see cref='System.ComponentModel.MemberDescriptor'/> class with the specified <see cref='System.ComponentModel.MemberDescriptor'/>.
        ///    </para>
        /// </devdoc>
        protected MemberDescriptor(MemberDescriptor descr) {
            this.name = descr.Name;
            this.displayName = this.name;
            this.nameHash = name.GetHashCode();
            
            this.attributes = new Attribute[descr.Attributes.Count];
            descr.Attributes.CopyTo(this.attributes, 0);
            
            attributesFiltered = true;
 
            this.originalAttributes = this.attributes;
        }
 
        /// <devdoc>
        ///    <para>
        ///       Initializes a new instance of the <see cref='System.ComponentModel.MemberDescriptor'/> class with the name in the specified
        ///    <see cref='System.ComponentModel.MemberDescriptor'/> and the attributes 
        ///       in both the old <see cref='System.ComponentModel.MemberDescriptor'/> and the <see cref='System.Attribute'/> array.
        ///    </para>
        /// </devdoc>
        protected MemberDescriptor(MemberDescriptor oldMemberDescriptor, Attribute[] newAttributes) {
            this.name = oldMemberDescriptor.Name;
            this.displayName = oldMemberDescriptor.DisplayName;
            this.nameHash = name.GetHashCode();
 
            ArrayList newArray = new ArrayList();
 
            if (oldMemberDescriptor.Attributes.Count != 0) {
                foreach (object o in oldMemberDescriptor.Attributes) {
                    newArray.Add(o);
                }
            }
 
            if (newAttributes != null) {
                foreach (object o in newAttributes) {
                    newArray.Add(o);
                }
            }
 
            this.attributes = new Attribute[ newArray.Count ];
            newArray.CopyTo( this.attributes, 0);
            attributesFiltered = false;
 
            this.originalAttributes = this.attributes;
        }
 
        /// <devdoc>
        ///    <para>
        ///       Gets or sets an array of
        ///       attributes.
        ///    </para>
        /// </devdoc>
        protected virtual Attribute[] AttributeArray {
            get {
                CheckAttributesValid();
                FilterAttributesIfNeeded();
                return attributes;
            }
            set {
                lock(lockCookie) {
                    attributes = value;
                    originalAttributes = value;
                    attributesFiltered = false;
                    attributeCollection = null;
                }
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Gets the collection of attributes for this member.
        ///    </para>
        /// </devdoc>
        public virtual AttributeCollection Attributes {
            get {
                CheckAttributesValid();
                AttributeCollection attrs = attributeCollection;
                if (attrs == null) {
                    lock(lockCookie) {
                        attrs = CreateAttributeCollection();
                        attributeCollection = attrs;
                    }
                }
                return attrs;
            }
        }
 
 
        /// <devdoc>
        ///    <para>
        ///       Gets the name of the category that the
        ///       member
        ///       belongs to, as specified in the <see cref='System.ComponentModel.CategoryAttribute'/>.
        ///    </para>
        /// </devdoc>
        public virtual string Category {
            get {
                if (category == null) {
                    category = ((CategoryAttribute)Attributes[typeof(CategoryAttribute)]).Category;
                }
                return category;
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Gets the description of
        ///       the member as specified in the <see cref='System.ComponentModel.DescriptionAttribute'/>.
        ///    </para>
        /// </devdoc>
        public virtual string Description {
            get {
                if (description == null) {
                    description = ((DescriptionAttribute) Attributes[typeof(DescriptionAttribute)]).Description;
                }
                return description;
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Gets a value indicating whether the member is browsable as specified in the
        ///    <see cref='System.ComponentModel.BrowsableAttribute'/>. 
        ///    </para>
        /// </devdoc>
        public virtual bool IsBrowsable {
            get {
                return((BrowsableAttribute)Attributes[typeof(BrowsableAttribute)]).Browsable;
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Gets the
        ///       name of the member.
        ///    </para>
        /// </devdoc>
        public virtual string Name {
            get {
                if (name == null) {
                    return "";
                }
                return name;
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Gets the hash
        ///       code for the name of the member as specified in <see cref='System.String.GetHashCode'/>.
        ///    </para>
        /// </devdoc>
        protected virtual int NameHashCode {
            get {
                return nameHash;
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Determines whether this member should be set only at
        ///       design time as specified in the <see cref='System.ComponentModel.DesignOnlyAttribute'/>.
        ///    </para>
        /// </devdoc>
        public virtual bool DesignTimeOnly {
            get {
                return(DesignOnlyAttribute.Yes.Equals(Attributes[typeof(DesignOnlyAttribute)]));
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Gets the name that can be displayed in a window like a
        ///       properties window.
        ///    </para>
        /// </devdoc>
        public virtual string DisplayName {
            get {
                DisplayNameAttribute displayNameAttr = Attributes[typeof(DisplayNameAttribute)] as DisplayNameAttribute;
                if (displayNameAttr == null || displayNameAttr.IsDefaultAttribute()) {
                    return displayName;
                }   
                return displayNameAttr.DisplayName;
            }
        }
 
        /// <devdoc>
        ///     Called each time we access the attribtes on
        ///     this member descriptor to give deriving classes
        ///     a chance to change them on the fly.
        /// </devdoc>
        private void CheckAttributesValid() {
            if (attributesFiltered) {
                if (metadataVersion != TypeDescriptor.MetadataVersion) {
                    attributesFilled = false;
                    attributesFiltered = false;
                    attributeCollection = null;
                }
            }
        }
 
        /// <include file='doc\MemberDescriptor.uex' path='docs/doc[@for="MemberDescriptor.CreateAttributeCollection"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Creates a collection of attributes using the
        ///       array of attributes that you passed to the constructor.
        ///    </para>
        /// </devdoc>
        protected virtual AttributeCollection CreateAttributeCollection() {
            return new AttributeCollection(AttributeArray);
        }
 
        /// <devdoc>
        ///    <para>
        ///       Compares this instance to the specified <see cref='System.ComponentModel.MemberDescriptor'/> to see if they are equivalent.
        ///       NOTE: If you make a change here, you likely need to change GetHashCode() as well.
        ///    </para>
        /// </devdoc>
        public override bool Equals(object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
 
            if (obj.GetType() != GetType()) {
                return false;
            }
 
            MemberDescriptor mdObj = (MemberDescriptor)obj;
            FilterAttributesIfNeeded();
            mdObj.FilterAttributesIfNeeded();
 
            if (mdObj.nameHash != nameHash) {
                return false;
            }
 
            if ((mdObj.category == null) != (category == null) ||
                (category != null && !mdObj.category.Equals(category))) {
                return false;
            }
 
            // VSO 149471 - Technically fixing this could cause a behavior change, so we are
            // adding a quirk in case anyone is bit by this and needs the old, buggy behavior.
            if (!LocalAppContextSwitches.MemberDescriptorEqualsReturnsFalseIfEquivalent) {
                if ((mdObj.description == null) != (description == null) ||
                    (description != null && !mdObj.description.Equals(description))) {
                    return false;
                }
            }
            else {
                if ((mdObj.description == null) != (description == null) ||
                    (description != null && !mdObj.category.Equals(description))) {
                    return false;
                }
            }
 
            if ((mdObj.attributes == null) != (attributes == null)) {
                return false;
            }
                                                
            bool sameAttrs = true;
 
            if (attributes != null) {
                if (attributes.Length != mdObj.attributes.Length) {
                    return false;
                }
                for (int i = 0; i < attributes.Length; i++) {
                    if (!attributes[i].Equals(mdObj.attributes[i])) {
                        sameAttrs = false;
                        break;
                    }
                }
            }
            return sameAttrs;
        }
 
        /// <devdoc>
        ///    <para>
        ///       In an inheriting class, adds the attributes of the inheriting class to the
        ///       specified list of attributes in the parent class.  For duplicate attributes,
        ///       the last one added to the list will be kept.
        ///    </para>
        /// </devdoc>
        protected virtual void FillAttributes(IList attributeList) {
            if (originalAttributes != null) {
                foreach (Attribute attr in originalAttributes) {
                    attributeList.Add(attr);
                }
            }
        }
 
        private void FilterAttributesIfNeeded() {
            if (!attributesFiltered) {
                IList list;
 
                if (!attributesFilled) {
                    list = new ArrayList();
                    try {
                        FillAttributes(list);
                    }
                    catch (System.Threading.ThreadAbortException) {
                        throw;
                    }
                    catch (Exception e) {
                        Debug.Fail(name + ">>" + e.ToString()); 
                    }
                }
                else {
                    list = new ArrayList(attributes);
                }
 
                Hashtable hash = new Hashtable(list.Count);
 
                foreach (Attribute attr in list) {
                    hash[attr.TypeId] = attr;
                }
 
                Attribute[] newAttributes = new Attribute[hash.Values.Count];
                hash.Values.CopyTo(newAttributes, 0);
 
                lock(lockCookie) {
                    attributes = newAttributes;
                    attributesFiltered = true;
                    attributesFilled = true;
                    metadataVersion = TypeDescriptor.MetadataVersion;
                }
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Finds the given method through reflection.  This method only looks for public methods.
        ///    </para>
        /// </devdoc>
        protected static MethodInfo FindMethod(Type componentClass, string name, Type[] args, Type returnType) {
            return FindMethod(componentClass, name, args, returnType, true);
        }
 
        /// <devdoc>
        ///    <para>
        ///       Finds the given method through reflection.
        ///    </para>
        /// </devdoc>
        protected static MethodInfo FindMethod(Type componentClass, string name, Type[] args, Type returnType, bool publicOnly) {
            MethodInfo result = null;
 
            if (publicOnly) {
                result = componentClass.GetMethod(name, args);
            }
            else {
                result = componentClass.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, args, null);
            }
            if (result != null && !result.ReturnType.IsEquivalentTo(returnType)) {
                result = null;
            }
            return result;
        }
 
        /// <devdoc>
        ///     Try to keep this reasonable in sync with Equals(). Specifically, 
        ///     if A.Equals(B) returns true, A & B should have the same hash code.
        /// </devdoc>
        public override int GetHashCode() {
            return nameHash;
        }
 
        /// <devdoc>
        ///     This method returns the object that should be used during invocation of members.
        ///     Normally the return value will be the same as the instance passed in.  If
        ///     someone associated another object with this instance, or if the instance is a
        ///     custom type descriptor, GetInvocationTarget may return a different value.
        /// </devdoc>
        protected virtual object GetInvocationTarget(Type type, object instance) {
 
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }
 
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
 
            return TypeDescriptor.GetAssociation(type, instance);
        }
 
        /// <devdoc>
        ///    <para>
        ///       Gets a component site
        ///       for the given component.
        ///    </para>
        /// </devdoc>
        protected static ISite GetSite(object component) {
            if (!(component is IComponent)) {
                return null;
            }
 
            return((IComponent)component).Site;
        }
 
        /// <devdoc>
        ///    <para>
        ///       Gets
        ///       the component
        ///       that a method should be invoked on.
        ///    </para>
        /// </devdoc>
        [Obsolete("This method has been deprecated. Use GetInvocationTarget instead.  http://go.microsoft.com/fwlink/?linkid=14202")]
        protected static object GetInvokee(Type componentClass, object component) {
 
            if (componentClass == null)
            {
                throw new ArgumentNullException("componentClass");
            }
 
            if (component == null)
            {
                throw new ArgumentNullException("component");
            }
 
            return TypeDescriptor.GetAssociation(componentClass, component);
        }
    }
}