File: System\Data\EntityModel\SchemaObjectModel\StructuredType.cs
Project: ndp\fx\src\DataEntity\System.Data.Entity.csproj (System.Data.Entity)
//---------------------------------------------------------------------
// <copyright file="StructuredType.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
 
namespace System.Data.EntityModel.SchemaObjectModel
{
    using System;
    using System.Data.Entity;
    using System.Data.Metadata.Edm;
    using System.Diagnostics;
    using System.Globalization;
    using System.Xml;
 
    /// <summary>
    /// Summary description for StructuredType.
    /// </summary>
    internal abstract class StructuredType : SchemaType
    {
        #region Instance Fields
        private bool? _baseTypeResolveResult;
        private string _unresolvedBaseType = null;
        private StructuredType _baseType = null;
        private bool _isAbstract = false;
        private SchemaElementLookUpTable<SchemaElement> _namedMembers = null;
        private ISchemaElementLookUpTable<StructuredProperty> _properties = null;
        #endregion
 
        #region Static Fields
        private static readonly char[] NameSeparators = new char[] { '.' };
        #endregion
 
        #region Public Properties
        /// <summary>
        /// 
        /// </summary>
        public StructuredType BaseType
        {
            get
            {
                return _baseType;
            }
            private set
            {
                _baseType = value;
            }
        }
 
        /// <summary>
        /// 
        /// 
        /// </summary>
        public ISchemaElementLookUpTable<StructuredProperty> Properties
        {
            get
            {
                if (_properties == null)
                {
                    _properties = new FilteredSchemaElementLookUpTable<StructuredProperty, SchemaElement>(NamedMembers);
                }
                return _properties;
            }
        }
 
        /// <summary>
        /// 
        /// 
        /// </summary>
        protected SchemaElementLookUpTable<SchemaElement> NamedMembers
        {
            get
            {
                if (_namedMembers == null)
                {
                    _namedMembers = new SchemaElementLookUpTable<SchemaElement>();
                }
                return _namedMembers;
            }
        }
 
        /// <summary>
        /// 
        /// </summary>
        public virtual bool IsTypeHierarchyRoot
        {
            get
            {
                Debug.Assert((BaseType == null && _unresolvedBaseType == null) ||
                             (BaseType != null && _unresolvedBaseType != null), "you are checking for the hierarchy root before the basetype has been set");
 
                // any type without a base is a base type
                return BaseType == null;
            }
        }
 
 
        /// <summary>
        /// 
        /// </summary>
        public bool IsAbstract
        {
            get
            {
                return _isAbstract;
            }
        }
 
 
        #endregion
 
        #region More Public Methods
        /// <summary>
        /// Find a property by name in the type hierarchy
        /// </summary>
        /// <param name="name">simple property name</param>
        /// <returns>the StructuredProperty object if name exists, null otherwise</returns>
        public StructuredProperty FindProperty(string name)
        {
            StructuredProperty property = Properties.LookUpEquivalentKey(name);
            if (property != null)
                return property;
 
            if (IsTypeHierarchyRoot)
                return null;
 
            return BaseType.FindProperty(name);
        }
 
 
        /// <summary>
        /// Determines whether this type is of the same type as baseType, 
        /// or is derived from baseType.
        /// </summary>
        /// <param name="baseType"></param>
        /// <returns>true if this type is of the baseType, false otherwise</returns>
        public bool IsOfType(StructuredType baseType)
        {
            StructuredType type = this;
 
            while (type != null && type != baseType)
            {
                type = type.BaseType;
            }
 
            return (type == baseType);
        }
        #endregion
 
        #region Protected Methods
        /// <summary>
        /// 
        /// </summary>
        internal override void ResolveTopLevelNames()
        {
            base.ResolveTopLevelNames();
 
            TryResolveBaseType();
 
            foreach (SchemaElement member in NamedMembers)
                member.ResolveTopLevelNames();
 
        }
 
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        internal override void Validate()
        {
            base.Validate();
 
            foreach (SchemaElement member in NamedMembers)
            {
                if (BaseType != null)
                {
                    StructuredType definingType;
                    SchemaElement definingMember;
                    string errorMessage = null;
                    if(HowDefined.AsMember == BaseType.DefinesMemberName(member.Name, out definingType, out definingMember))
                    {
                        errorMessage = System.Data.Entity.Strings.DuplicateMemberName(member.Name, FQName, definingType.FQName);
                    }
                    if (errorMessage != null)
                        member.AddError(ErrorCode.AlreadyDefined, EdmSchemaErrorSeverity.Error, errorMessage);
                }
 
                member.Validate();
            }
        }
 
        /// <summary>
        /// 
        /// </summary>
        /// <param name="parentElement"></param>
        protected StructuredType(Schema parentElement)
            : base(parentElement)
        {
        }
 
        /// <summary>
        /// Add a member to the type
        /// </summary>
        /// <param name="newMember">the member being added</param>
        protected void AddMember(SchemaElement newMember)
        {
            Debug.Assert(newMember != null, "newMember parameter is null");
 
            if (string.IsNullOrEmpty(newMember.Name))
            {
                // this is an error condition that has already been reported.
                return;
            }
 
            if (this.Schema.DataModel != SchemaDataModelOption.ProviderDataModel &&
                 Utils.CompareNames(newMember.Name, Name) == 0)
            {
                newMember.AddError(ErrorCode.BadProperty, EdmSchemaErrorSeverity.Error,
                    System.Data.Entity.Strings.InvalidMemberNameMatchesTypeName(newMember.Name, FQName));
            }
 
            NamedMembers.Add(newMember, true, Strings.PropertyNameAlreadyDefinedDuplicate);
        }
 
 
        /// <summary>
        /// See if a name is a member in a type or any of its base types
        /// </summary>
        /// <param name="name">name to look for</param>
        /// <param name="definingType">if defined, the type that defines it</param>
        /// <param name="definingMember">if defined, the member that defines it</param>
        /// <returns>how name was defined</returns>
        private HowDefined DefinesMemberName(string name, out StructuredType definingType, out SchemaElement definingMember)
        {
            if (NamedMembers.ContainsKey(name))
            {
                definingType = this;
                definingMember = NamedMembers[name];
                return HowDefined.AsMember;
            }
 
            definingMember = NamedMembers.LookUpEquivalentKey(name);
            Debug.Assert(definingMember == null, "we allow the scenario that members can have same name but different cases");
 
            if (IsTypeHierarchyRoot)
            {
                definingType = null;
                definingMember = null;
                return HowDefined.NotDefined;
            }
 
            return BaseType.DefinesMemberName(name, out definingType, out definingMember);
        }
        #endregion
 
        #region Protected Properties
        /// <summary>
        /// 
        /// </summary>
        protected string UnresolvedBaseType
        {
            get
            {
                return _unresolvedBaseType;
            }
            set
            {
                _unresolvedBaseType = value;
            }
        }
 
        protected override bool HandleElement(XmlReader reader)
        {
            if (base.HandleElement(reader))
            {
                return true;
            }
            else if (CanHandleElement(reader, XmlConstants.Property))
            {
                HandlePropertyElement(reader);
                return true;
            }
            return false;
        }
 
        protected override bool HandleAttribute(XmlReader reader)
        {
            if (base.HandleAttribute(reader))
            {
                return true;
            }
            else if (CanHandleAttribute(reader, XmlConstants.BaseType))
            {
                HandleBaseTypeAttribute(reader);
                return true;
            }
            else if (CanHandleAttribute(reader, XmlConstants.Abstract))
            {
                HandleAbstractAttribute(reader);
                return true;
            }
 
            return false;
        }
        #endregion
 
        #region Private Methods
        /// <summary>
        /// 
        /// </summary>
        private bool TryResolveBaseType()
        {
            if (_baseTypeResolveResult.HasValue)
            {
                return _baseTypeResolveResult.Value;
            }
 
            if (BaseType != null)
            {
                _baseTypeResolveResult = true;
                return _baseTypeResolveResult.Value;
            }
 
            if (UnresolvedBaseType == null)
            {
                _baseTypeResolveResult = true;
                return _baseTypeResolveResult.Value;
            }
 
            SchemaType element;
            if (!Schema.ResolveTypeName(this, UnresolvedBaseType, out element))
            {
                _baseTypeResolveResult = false;
                return _baseTypeResolveResult.Value;
            }
 
            BaseType = element as StructuredType;
            if (BaseType == null)
            {
                AddError(ErrorCode.InvalidBaseType, EdmSchemaErrorSeverity.Error,
                    System.Data.Entity.Strings.InvalidBaseTypeForStructuredType(UnresolvedBaseType, FQName));
                _baseTypeResolveResult = false;
                return _baseTypeResolveResult.Value;
            }
 
            // verify that creating this link to the base type will not introduce a cycle;
            // if so, break the link and add an error
            if (CheckForInheritanceCycle())
            {
                BaseType = null;
 
                AddError(ErrorCode.CycleInTypeHierarchy, EdmSchemaErrorSeverity.Error,
                    System.Data.Entity.Strings.CycleInTypeHierarchy(FQName));
                _baseTypeResolveResult = false;
                return _baseTypeResolveResult.Value;
            }
 
            _baseTypeResolveResult = true;
            return true;
        }
 
        /// <summary>
        /// 
        /// </summary>
        /// <param name="reader"></param>
        private void HandleBaseTypeAttribute(XmlReader reader)
        {
            Debug.Assert(UnresolvedBaseType == null, string.Format(CultureInfo.CurrentCulture, "{0} is already defined", reader.Name));
 
            string baseType;
            if (!Utils.GetDottedName(this.Schema, reader, out baseType))
                return;
 
            UnresolvedBaseType = baseType;
        }
 
        /// <summary>
        /// 
        /// </summary>
        /// <param name="reader"></param>
        private void HandleAbstractAttribute(XmlReader reader)
        {
            HandleBoolAttribute(reader, ref _isAbstract);
        }
 
        /// <summary>
        /// 
        /// </summary>
        /// <param name="reader"></param>
        private void HandlePropertyElement(XmlReader reader)
        {
            StructuredProperty property = new StructuredProperty(this);
 
            property.Parse(reader);
 
            AddMember(property);
        }
 
        /// <summary>
        /// Determine if a cycle exists in the type hierarchy: use two pointers to
        /// walk the chain, if one catches up with the other, we have a cycle.
        /// </summary>
        /// <returns>true if a cycle exists in the type hierarchy, false otherwise</returns>
        private bool CheckForInheritanceCycle()
        {
            StructuredType baseType = BaseType;
            Debug.Assert(baseType != null);
 
            StructuredType ref1 = baseType;
            StructuredType ref2 = baseType;
 
            do
            {
                ref2 = ref2.BaseType;
 
                if (Object.ReferenceEquals(ref1, ref2))
                    return true;
 
                if (ref1 == null)
                    return false;
 
                ref1 = ref1.BaseType;
 
                if (ref2 != null)
                    ref2 = ref2.BaseType;
            }
            while (ref2 != null);
 
            return false;
        }
 
        #endregion
 
        #region Private Properties
        #endregion
 
        private enum HowDefined
        {
            NotDefined,
            AsMember,
        }
    }
}