File: System\Data\EntityModel\Emitters\AttributeEmitter.cs
Project: ndp\fx\src\DataWeb\Design\System.Data.Services.Design.csproj (System.Data.Services.Design)
//---------------------------------------------------------------------
// <copyright file="AttributeEmitter.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
 
using System.CodeDom;
using System.Collections.Generic;
using System.Data.Metadata.Edm;
using System.Data.Services.Common;
using System.Data.Services.Design;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
 
namespace System.Data.EntityModel.Emitters
{
    /// <summary>
    /// Summary description for AttributeEmitter.
    /// </summary>
    internal sealed partial class AttributeEmitter
    {
        private const string _generatorVersion = "1.0.0";
        private const string _generatorName = "System.Data.Services.Design";
 
        private static readonly CodeAttributeDeclaration _generatedCodeAttribute = new CodeAttributeDeclaration(
            new CodeTypeReference(typeof(System.CodeDom.Compiler.GeneratedCodeAttribute)),
            new CodeAttributeArgument(new CodePrimitiveExpression(_generatorName)),
            new CodeAttributeArgument(new CodePrimitiveExpression(_generatorVersion)));
 
        TypeReference _typeReference;
 
        internal TypeReference TypeReference
        {
            get { return _typeReference; }
        }
 
        internal AttributeEmitter(TypeReference typeReference)
        {
            _typeReference = typeReference;
        }
 
        /// <summary>
        /// The method to be called to create the type level attributes for the ItemTypeEmitter
        /// </summary>
        /// <param name="emitter">The strongly typed emitter</param>
        /// <param name="typeDecl">The type declaration to add the attribues to.</param>
        public void EmitTypeAttributes(EntityTypeEmitter emitter, CodeTypeDeclaration typeDecl)
        {
            Debug.Assert(emitter != null, "emitter should not be null");
            Debug.Assert(typeDecl != null, "typeDecl should not be null");
 
            if (emitter.Generator.Version != DataServiceCodeVersion.V1)
            {
                EmitEpmAttributesForEntityType(emitter.Generator.EdmItemCollection, emitter.Item, typeDecl);
                EmitStreamAttributesForEntityType(emitter.Item, typeDecl);
            }
 
            object[] keys = emitter.Item.KeyMembers.Select(km => (object) km.Name).ToArray();
            typeDecl.CustomAttributes.Add(EmitSimpleAttribute(Utils.WebFrameworkCommonNamespace + "." + "DataServiceKeyAttribute", keys));
        }
 
 
        /// <summary>
        /// The method to be called to create the type level attributes for the StructuredTypeEmitter
        /// </summary>
        /// <param name="emitter">The strongly typed emitter</param>
        /// <param name="typeDecl">The type declaration to add the attribues to.</param>
        public void EmitTypeAttributes(StructuredTypeEmitter emitter, CodeTypeDeclaration typeDecl)
        {
            Debug.Assert(emitter != null, "emitter should not be null");
            Debug.Assert(typeDecl != null, "typeDecl should not be null");
 
            // nothing to do here yet
        }
 
        /// <summary>
        /// The method to be called to create the type level attributes for the SchemaTypeEmitter
        /// </summary>
        /// <param name="emitter">The strongly typed emitter</param>
        /// <param name="typeDecl">The type declaration to add the attribues to.</param>
        public void EmitTypeAttributes(SchemaTypeEmitter emitter, CodeTypeDeclaration typeDecl)
        {
            Debug.Assert(emitter != null, "emitter should not be null");
            Debug.Assert(typeDecl != null, "typeDecl should not be null");
        }
 
        /// <summary>
        /// The method to be called to create the property level attributes for the PropertyEmitter
        /// </summary>
        /// <param name="emitter">The strongly typed emitter</param>
        /// <param name="propertyDecl">The type declaration to add the attribues to.</param>
        /// <param name="additionalAttributes">Additional attributes to emit</param>
        public void EmitPropertyAttributes(PropertyEmitter emitter,
                                           CodeMemberProperty propertyDecl,
                                           List<CodeAttributeDeclaration> additionalAttributes)
        {
            if (additionalAttributes != null && additionalAttributes.Count > 0)
            {
                try
                {
                    propertyDecl.CustomAttributes.AddRange(additionalAttributes.ToArray());
                }
                catch (ArgumentNullException e)
                {
                    emitter.Generator.AddError(Strings.InvalidAttributeSuppliedForProperty(emitter.Item.Name),
                                               ModelBuilderErrorCode.InvalidAttributeSuppliedForProperty,
                                               EdmSchemaErrorSeverity.Error,
                                               e);
                }
            }
        }
 
        /// <summary>
        /// The method to be called to create the type level attributes for the NestedTypeEmitter
        /// </summary>
        /// <param name="emitter">The strongly typed emitter</param>
        /// <param name="typeDecl">The type declaration to add the attribues to.</param>
        public void EmitTypeAttributes(ComplexTypeEmitter emitter, CodeTypeDeclaration typeDecl)
        {
            Debug.Assert(emitter != null, "emitter should not be null");
            Debug.Assert(typeDecl != null, "typeDecl should not be null");
 
            // not emitting System.Runtime.Serializaton.DataContractAttribute
            // not emitting System.Serializable
        }
 
        #region Static Methods
        /// <summary>
        /// 
        /// </summary>
        /// <param name="attributeType"></param>
        /// <param name="arguments"></param>
        /// <returns></returns>
        public CodeAttributeDeclaration EmitSimpleAttribute(string attributeType, params object[] arguments)
        {
            CodeAttributeDeclaration attribute = new CodeAttributeDeclaration(TypeReference.FromString(attributeType, true));
 
            AddAttributeArguments(attribute, arguments);
 
            return attribute;
        }
 
        /// <summary>
        /// 
        /// </summary>
        /// <param name="attribute"></param>
        /// <param name="arguments"></param>
        public static void AddAttributeArguments(CodeAttributeDeclaration attribute, object[] arguments)
        {
            foreach (object argument in arguments)
            {
                CodeExpression expression = argument as CodeExpression;
                if (expression == null)
                    expression = new CodePrimitiveExpression(argument);
                attribute.Arguments.Add(new CodeAttributeArgument(expression));
            }
        }
 
        /// <summary>
        /// Adds an XmlIgnore attribute to the given property declaration.  This is 
        /// used to explicitly skip certain properties during XML serialization.
        /// </summary>
        /// <param name="propertyDecl">the property to mark with XmlIgnore</param>
        public void AddIgnoreAttributes(CodeMemberProperty propertyDecl)
        {
            // not emitting System.Xml.Serialization.XmlIgnoreAttribute
            // not emitting System.Xml.Serialization.SoapIgnoreAttribute
        }
 
        /// <summary>
        /// Adds an Browsable(false) attribute to the given property declaration.
        /// This is used to explicitly avoid display property in the PropertyGrid.
        /// </summary>
        /// <param name="propertyDecl">the property to mark with XmlIgnore</param>
        public void AddBrowsableAttribute(CodeMemberProperty propertyDecl)
        {
            // not emitting System.ComponentModel.BrowsableAttribute
        }
 
        #endregion
 
        /// <summary>
        /// Add the GeneratedCode attribute to the code type member
        /// </summary>
        /// <param name="ctm">the code type member</param>
        public static void AddGeneratedCodeAttribute(CodeTypeMember ctm)
        {
            ctm.CustomAttributes.Add(_generatedCodeAttribute);
        }
 
        /// <summary>Given a type detects if it is an open type or not</summary>
        /// <param name="entityType">Input type</param>
        /// <returns>true if it is an open type, false otherwise</returns>
        private static bool IsOpenType(StructuralType entityType)
        {
            MetadataProperty isOpenTypeProperty = entityType.MetadataProperties.FirstOrDefault(x => x.Name == System.Data.Services.XmlConstants.EdmV1dot2Namespace + ":" + System.Data.Services.XmlConstants.DataWebOpenTypeAttributeName);
            if (isOpenTypeProperty != null)
            {
                bool isOpenType;
                if (!Boolean.TryParse(Convert.ToString(isOpenTypeProperty.Value, CultureInfo.InvariantCulture), out isOpenType))
                {
                    throw new InvalidOperationException(Strings.ObjectContext_OpenTypePropertyValueIsNotCorrect(System.Data.Services.XmlConstants.DataWebOpenTypeAttributeName, entityType.Name));
                }
                
                return isOpenType;
            }
            else
            {
                return false;
            }
        }
 
        /// <summary>
        /// Checks if the given path corresponds to some open property on the <paramref name="baseEntityType"/>
        /// </summary>
        /// <param name="baseEntityType">Type to check the path for</param>
        /// <param name="sourcePath">Input property path</param>
        /// <returns>true if there is some open property corresponding to the path, false otherwise</returns>
        bool IsOpenPropertyOnPath(StructuralType baseEntityType, String sourcePath)
        {
            Debug.Assert(baseEntityType != null, "Expecting non-null entity type");
            if (String.IsNullOrEmpty(sourcePath))
            {
                return false;
            }
            
            String[] propertyPath = sourcePath.Split('/');
            EdmMember entityProperty = baseEntityType.Members.SingleOrDefault(p => p.Name == propertyPath[0]);
 
            if (entityProperty == null)
            {
                if (baseEntityType.BaseType != null)
                {
                    return IsOpenPropertyOnPath(baseEntityType.BaseType as StructuralType, sourcePath);
                }
                else
                {
                    return IsOpenType(baseEntityType);
                }
            }
            else
            {
                StructuralType entityPropertyType = entityProperty.TypeUsage.EdmType as StructuralType;
                if (entityPropertyType != null)
                {
                    return IsOpenPropertyOnPath(entityPropertyType, String.Join("/", propertyPath, 1, propertyPath.Length - 1));
                }
                else
                {
                    return false;
                }
            }        
        }
 
        /// <summary>
        /// Obtains the entity property corresponding to a given sourcePath
        /// </summary>
        /// <param name="baseEntityType">Entity type in which to look for property</param>
        /// <param name="sourcePath">Source Path</param>
        /// <returns>EdmMember object corresponding to the property given through source path</returns>
        private static EdmMember GetEntityPropertyFromEpmPath(StructuralType baseEntityType, String sourcePath)
        {
            Debug.Assert(baseEntityType != null, "Expecting non-null entity type");
 
            String[] propertyPath = sourcePath.Split('/');
 
            if (!baseEntityType.Members.Any(p => p.Name == propertyPath[0]))
            {
                return baseEntityType.BaseType != null ? GetEntityPropertyFromEpmPath(baseEntityType.BaseType as StructuralType, sourcePath) : null;
            }
            else
            {
                EdmMember entityProperty = null;
                foreach (var pathSegment in propertyPath)
                {
                    if (baseEntityType == null)
                    {
                        return null;
                    }
                    
                    entityProperty = baseEntityType.Members.SingleOrDefault(p => p.Name == pathSegment);
                    
                    if (entityProperty == null)
                    {
                        return null;
                    }
                    
                    baseEntityType = entityProperty.TypeUsage.EdmType as StructuralType;
                }
 
                return entityProperty;
            }
        }
 
        /// <summary>
        /// Returns a sequence of attributes corresponding to a complex type with recursion
        /// </summary>
        /// <param name="complexProperty">Complex typed property</param>
        /// <param name="epmSourcePath">Source path</param>
        /// <param name="epmTargetPath">Target path</param>
        /// <param name="epmNsPrefix">Namespace prefix</param>
        /// <param name="epmNsUri">Namespace Uri</param>
        /// <param name="epmKeepContent">KeepInContent setting</param>
        /// <returns>Sequence of entity property mapping information for complex type properties</returns>
        private static IEnumerable<EntityPropertyMappingAttribute> GetEpmAttrsFromComplexProperty(
            EdmMember complexProperty,
            String epmSourcePath,
            String epmTargetPath,
            String epmNsPrefix,
            String epmNsUri,
            bool epmKeepContent)
        {
            Debug.Assert(complexProperty != null, "Expecting non-null complex property");
 
            ComplexType complexType = complexProperty.TypeUsage.EdmType as ComplexType;
            if (complexType == null)
            {
                throw new ArgumentException(Strings.ExpectingComplexTypeForMember(complexProperty.Name, complexProperty.DeclaringType.Name));
            }
 
            foreach (EdmMember subProperty in complexType.Properties)
            {
                String sourcePath = epmSourcePath + "/" + subProperty.Name;
                String targetPath = epmTargetPath + "/" + subProperty.Name;
 
                if (subProperty.TypeUsage.EdmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType)
                {
                    foreach (EntityPropertyMappingAttribute epmAttr in GetEpmAttrsFromComplexProperty(subProperty, sourcePath, targetPath, epmNsPrefix, epmNsUri, epmKeepContent))
                    {
                        yield return epmAttr;
                    }
                }
                else
                {
                    yield return new EntityPropertyMappingAttribute(
                                        sourcePath,
                                        targetPath,
                                        epmNsPrefix,
                                        epmNsUri,
                                        epmKeepContent);
                }
            }
        }
 
        /// <summary>
        /// Given a resource type, builds the EntityPropertyMappingInfo for each EntityPropertyMappingAttribute on it
        /// </summary>
        /// <param name="itemCollection">EFx metadata item collection that has been loaded</param>
        /// <param name="entityType">Entity type for which EntityPropertyMappingAttribute discovery is happening</param>
        /// <param name="typeDecl">Type declaration to add the attributes to</param>
        private void EmitEpmAttributesForEntityType(EdmItemCollection itemCollection, EntityType entityType, CodeTypeDeclaration typeDecl)
        {
            // Get epm information provided at the entity type declaration level
            IEnumerable<MetadataProperty> extendedProperties = entityType.MetadataProperties.Where(mp => mp.PropertyKind == PropertyKind.Extended);
 
            foreach (EpmPropertyInformation propertyInformation in GetEpmPropertyInformation(extendedProperties, entityType.Name, null))
            {
                EdmMember redefinedProperty = GetEntityPropertyFromEpmPath(entityType, propertyInformation.SourcePath);
                if (redefinedProperty == null)
                {
                    if (IsOpenPropertyOnPath(entityType, propertyInformation.SourcePath))
                    {
                        EmitEpmAttributeForEntityProperty(
                            propertyInformation,
                            new EdmInfo { IsComplex = false, Member = null },
                            typeDecl);
                    }
                    else
                    {
                        throw new InvalidOperationException(Strings.ObjectContext_UnknownPropertyNameInEpmAttributesType(propertyInformation.SourcePath, entityType.Name));
                    }
                }
                else
                {
                    EmitEpmAttributeForEntityProperty(
                        propertyInformation,
                        new EdmInfo { IsComplex = redefinedProperty.TypeUsage.EdmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType, Member = redefinedProperty }, 
                        typeDecl);
                }
            }
 
            // Get epm information provided at the entity type property level
            foreach (EdmMember member in entityType.Members.Where(m => m.DeclaringType == entityType))
            {
                EdmMember entityProperty = entityType.Properties.SingleOrDefault(p => p.DeclaringType == entityType && p.Name == member.Name);
                IEnumerable<MetadataProperty> extendedMemberProperties = member.MetadataProperties.Where(mdp => mdp.PropertyKind == PropertyKind.Extended);
 
                foreach (EpmPropertyInformation propertyInformation in GetEpmPropertyInformation(extendedMemberProperties, entityType.Name, member.Name))
                {
                    EdmMember entityPropertyCurrent = entityProperty;
                    if (entityProperty.TypeUsage.EdmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType && propertyInformation.PathGiven)
                    {
                        String originalPath = propertyInformation.SourcePath;
                        propertyInformation.SourcePath = entityProperty.Name + "/" + propertyInformation.SourcePath;
                        entityPropertyCurrent = GetEntityPropertyFromEpmPath(entityType, propertyInformation.SourcePath);
                        if (entityPropertyCurrent == null)
                        {
                            if (IsOpenPropertyOnPath(entityProperty.TypeUsage.EdmType as StructuralType, originalPath))
                            {
                                EmitEpmAttributeForEntityProperty(
                                    propertyInformation,
                                    new EdmInfo { IsComplex = false, Member = null },
                                    typeDecl);
                                continue;                                    
                            }
                            else
                            {
                                throw new InvalidOperationException(Strings.ObjectContext_UnknownPropertyNameInEpmAttributesMember(originalPath, member.Name, entityType.Name));
                            }
                        }
                    }
 
                    EmitEpmAttributeForEntityProperty(
                        propertyInformation, 
                        new EdmInfo { IsComplex = entityPropertyCurrent.TypeUsage.EdmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType, Member = entityPropertyCurrent }, 
                        typeDecl);
                }
            }
        }
 
        /// <summary>
        /// Given a resource type and its resource proeperty builds the EntityPropertyMappingInfo for the EntityPropertyMappingAttribute on it
        /// </summary>
        /// <param name="propertyInformation">EPM information for current property</param>
        /// <param name="entityProperty">Property for which to get the information</param>
        /// <param name="typeDecl">Type declaration to add the attributes to</param>
        private void EmitEpmAttributeForEntityProperty(
            EpmPropertyInformation propertyInformation, 
            EdmInfo entityProperty, 
            CodeTypeDeclaration typeDecl)
        {
            if (propertyInformation.IsAtom)
            {
                if (entityProperty.IsComplex)
                {
                    throw new InvalidOperationException(Strings.ObjectContext_SyndicationMappingForComplexPropertiesNotAllowed);
                }
                else
                {
                    EntityPropertyMappingAttribute epmAttr = new EntityPropertyMappingAttribute(
                                        propertyInformation.SourcePath,
                                        propertyInformation.SyndicationItem,
                                        propertyInformation.ContentKind,
                                        propertyInformation.KeepInContent);
 
                    this.AddEpmAttributeToTypeDeclaration(epmAttr, typeDecl);
                }
            }
            else
            {
                if (entityProperty.IsComplex)
                {
                    foreach (EntityPropertyMappingAttribute epmAttr in GetEpmAttrsFromComplexProperty(
                                                                        entityProperty.Member,
                                                                        propertyInformation.SourcePath,
                                                                        propertyInformation.TargetPath,
                                                                        propertyInformation.NsPrefix,
                                                                        propertyInformation.NsUri,
                                                                        propertyInformation.KeepInContent))
                    {
                        this.AddEpmAttributeToTypeDeclaration(epmAttr, typeDecl);
                    }
                }
                else
                {
                    EntityPropertyMappingAttribute epmAttr = new EntityPropertyMappingAttribute(
                                        propertyInformation.SourcePath,
                                        propertyInformation.TargetPath,
                                        propertyInformation.NsPrefix,
                                        propertyInformation.NsUri,
                                        propertyInformation.KeepInContent);
 
                    this.AddEpmAttributeToTypeDeclaration(epmAttr, typeDecl);
                }
            }
        }
 
        /// <summary>Creates an EntityPropertyMappingAttribute and adds it to the <paramref name="typeDecl"/></summary>
        /// <param name="epmAttr">Attribute to add</param>
        /// <param name="typeDecl">Type declaration for which the attribute is generated</param>
        private void AddEpmAttributeToTypeDeclaration(EntityPropertyMappingAttribute epmAttr, CodeTypeDeclaration typeDecl)
        {
            if (epmAttr.TargetSyndicationItem != SyndicationItemProperty.CustomProperty)
            {
                var syndicationItem = new CodeFieldReferenceExpression(
                                        new CodeTypeReferenceExpression(typeof(SyndicationItemProperty)),
                                        epmAttr.TargetSyndicationItem.ToString());
                var contentKind = new CodeFieldReferenceExpression(
                                        new CodeTypeReferenceExpression(typeof(SyndicationTextContentKind)),
                                        epmAttr.TargetTextContentKind.ToString());
 
                CodeAttributeDeclaration attribute = new CodeAttributeDeclaration(
                                                            TypeReference.FromString(
                                                                    Utils.WebFrameworkCommonNamespace + "." + "EntityPropertyMappingAttribute",
                                                                    true));
                AddAttributeArguments(attribute, new object[] { epmAttr.SourcePath, syndicationItem, contentKind, epmAttr.KeepInContent });
                typeDecl.CustomAttributes.Add(attribute);
            }
            else
            {
                CodeAttributeDeclaration attribute = new CodeAttributeDeclaration(
                                                            TypeReference.FromString(
                                                                    Utils.WebFrameworkCommonNamespace + "." + "EntityPropertyMappingAttribute",
                                                                    true));
                AddAttributeArguments(attribute, new object[] { epmAttr.SourcePath, epmAttr.TargetPath, epmAttr.TargetNamespacePrefix, epmAttr.TargetNamespaceUri, epmAttr.KeepInContent });
                typeDecl.CustomAttributes.Add(attribute);
            }
        }
        
        /// <summary>
        /// Given a resource type, generates the HasStreamAttribute for it
        /// </summary>
        /// <param name="entityType">Entity type for which HasStreamAttribute discovery is happening</param>
        /// <param name="typeDecl">Type declaration to add the attributes to</param>
        private void EmitStreamAttributesForEntityType(EntityType entityType, CodeTypeDeclaration typeDecl)
        {
            IEnumerable<MetadataProperty> hasStreamMetadataProperties =
                entityType.MetadataProperties.Where(mp => mp.PropertyKind == PropertyKind.Extended &&
                    mp.Name == System.Data.Services.XmlConstants.DataWebMetadataNamespace + ":" + System.Data.Services.XmlConstants.DataWebAccessHasStreamAttribute);
            MetadataProperty hasStreamMetadataProperty = null;
            foreach (MetadataProperty p in hasStreamMetadataProperties)
            {
                if (hasStreamMetadataProperty != null)
                {
                    throw new InvalidOperationException(
                        Strings.ObjectContext_MultipleValuesForSameExtendedAttributeType(
                            System.Data.Services.XmlConstants.DataWebAccessHasStreamAttribute, 
                            entityType.Name));
                }
                hasStreamMetadataProperty = p;
            }
            if (hasStreamMetadataProperty == null)
            {
                return;
            }
            if (!String.Equals(Convert.ToString(hasStreamMetadataProperty.Value, CultureInfo.InvariantCulture), 
                    System.Data.Services.XmlConstants.DataWebAccessDefaultStreamPropertyValue, StringComparison.Ordinal))
            {
                return;
            }
 
            CodeAttributeDeclaration attribute = new CodeAttributeDeclaration(
                                                        TypeReference.FromString(
                                                            Utils.WebFrameworkCommonNamespace + "." + "HasStreamAttribute",
                                                            true));
            typeDecl.CustomAttributes.Add(attribute);
        }
 
        /// <summary>Edm Member information used for generating attribute, necessary for supporting 
        /// open types which can potentioall not have any member so EdmMember property can be null
        /// </summary>
        private sealed class EdmInfo
        {
            /// <summary>Is the given type a complex type</summary>
            public bool IsComplex 
            { 
                get; 
                set; 
            }
            
            /// <summary>Corresponding EdmMember</summary>
            public EdmMember Member 
            { 
                get; 
                set; 
            }
        }
        
    }
}