File: System\Runtime\Serialization\EnumDataContract.cs
Project: ndp\cdf\src\WCF\Serialization\System.Runtime.Serialization.csproj (System.Runtime.Serialization)
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------
namespace System.Runtime.Serialization
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Reflection;
    using System.Threading;
    using System.Text;
    using System.Xml;
    using System.Security;
 
#if USE_REFEMIT
    public sealed class EnumDataContract : DataContract
#else
    internal sealed class EnumDataContract : DataContract
#endif
    {
        [Fx.Tag.SecurityNote(Critical = "Holds instance of CriticalHelper which keeps state that is cached statically for serialization."
            + " Static fields are marked SecurityCritical or readonly to prevent data from being modified or leaked to other components in appdomain.")]
        [SecurityCritical]
        EnumDataContractCriticalHelper helper;
 
        [Fx.Tag.SecurityNote(Critical = "Initializes SecurityCritical field 'helper'.",
            Safe = "Doesn't leak anything.")]
        [SecuritySafeCritical]
        internal EnumDataContract()
            : base(new EnumDataContractCriticalHelper())
        {
            helper = base.Helper as EnumDataContractCriticalHelper;
        }
 
        [Fx.Tag.SecurityNote(Critical = "Initializes SecurityCritical field 'helper'.",
            Safe = "Doesn't leak anything.")]
        [SecuritySafeCritical]
        internal EnumDataContract(Type type)
            : base(new EnumDataContractCriticalHelper(type))
        {
            helper = base.Helper as EnumDataContractCriticalHelper;
        }
 
        [Fx.Tag.SecurityNote(Critical = "Accesses SecurityCritical static cache to look up base contract name for a type.",
            Safe = "Read only access.")]
        [SecuritySafeCritical]
        static internal XmlQualifiedName GetBaseContractName(Type type)
        {
            return EnumDataContractCriticalHelper.GetBaseContractName(type);
        }
 
        [Fx.Tag.SecurityNote(Critical = "Accesses SecurityCritical static cache to look up a base contract name.",
            Safe = "Read only access.")]
        [SecuritySafeCritical]
        static internal Type GetBaseType(XmlQualifiedName baseContractName)
        {
            return EnumDataContractCriticalHelper.GetBaseType(baseContractName);
        }
 
        internal XmlQualifiedName BaseContractName
        {
            [Fx.Tag.SecurityNote(Critical = "Fetches the critical BaseContractName property.",
                Safe = "BaseContractName only needs to be protected for write.")]
            [SecuritySafeCritical]
            get { return helper.BaseContractName; }
 
            [Fx.Tag.SecurityNote(Critical = "Sets the critical BaseContractName property.")]
            [SecurityCritical]
            set { helper.BaseContractName = value; }
        }
 
        internal List<DataMember> Members
        {
            [Fx.Tag.SecurityNote(Critical = "Fetches the critical Members property.",
                Safe = "Members only needs to be protected for write.")]
            [SecuritySafeCritical]
            get { return helper.Members; }
 
            [Fx.Tag.SecurityNote(Critical = "Sets the critical Members property.")]
            [SecurityCritical]
            set { helper.Members = value; }
        }
 
        internal List<long> Values
        {
            [Fx.Tag.SecurityNote(Critical = "Fetches the critical Values property.",
                Safe = "Values only needs to be protected for write.")]
            [SecuritySafeCritical]
            get { return helper.Values; }
 
            [Fx.Tag.SecurityNote(Critical = "Sets the critical Values property.")]
            [SecurityCritical]
            set { helper.Values = value; }
        }
 
        internal bool IsFlags
        {
            [Fx.Tag.SecurityNote(Critical = "Fetches the critical IsFlags property.",
                Safe = "IsFlags only needs to be protected for write.")]
            [SecuritySafeCritical]
            get { return helper.IsFlags; }
 
            [Fx.Tag.SecurityNote(Critical = "Sets the critical IsFlags property.")]
            [SecurityCritical]
            set { helper.IsFlags = value; }
        }
 
        internal bool IsULong
        {
            [Fx.Tag.SecurityNote(Critical = "Fetches the critical IsULong property.",
                Safe = "IsULong only needs to be protected for write.")]
            [SecuritySafeCritical]
            get { return helper.IsULong; }
        }
 
        XmlDictionaryString[] ChildElementNames
        {
            [Fx.Tag.SecurityNote(Critical = "Fetches the critical ChildElementNames property.",
                Safe = "ChildElementNames only needs to be protected for write.")]
            [SecuritySafeCritical]
            get { return helper.ChildElementNames; }
        }
 
        internal override bool CanContainReferences
        {
            get { return false; }
        }
 
        [Fx.Tag.SecurityNote(Critical = "Holds all state used for (de)serializing enums."
            + " Since the data is cached statically, we lock down access to it.")]
        [SecurityCritical(SecurityCriticalScope.Everything)]
        class EnumDataContractCriticalHelper : DataContract.DataContractCriticalHelper
        {
            static Dictionary<Type, XmlQualifiedName> typeToName;
            static Dictionary<XmlQualifiedName, Type> nameToType;
 
            XmlQualifiedName baseContractName;
            List<DataMember> members;
            List<long> values;
            bool isULong;
            bool isFlags;
            bool hasDataContract;
            XmlDictionaryString[] childElementNames;
 
            static EnumDataContractCriticalHelper()
            {
                typeToName = new Dictionary<Type, XmlQualifiedName>();
                nameToType = new Dictionary<XmlQualifiedName, Type>();
                Add(typeof(sbyte), "byte");
                Add(typeof(byte), "unsignedByte");
                Add(typeof(short), "short");
                Add(typeof(ushort), "unsignedShort");
                Add(typeof(int), "int");
                Add(typeof(uint), "unsignedInt");
                Add(typeof(long), "long");
                Add(typeof(ulong), "unsignedLong");
            }
 
            [SuppressMessage(FxCop.Category.Usage, "CA2301:EmbeddableTypesInContainersRule", MessageId = "typeToName", Justification = "No need to support type equivalence here.")]
            static internal void Add(Type type, string localName)
            {
                XmlQualifiedName stableName = CreateQualifiedName(localName, Globals.SchemaNamespace);
                typeToName.Add(type, stableName);
                nameToType.Add(stableName, type);
            }
 
            static internal XmlQualifiedName GetBaseContractName(Type type)
            {
                XmlQualifiedName retVal = null;
                typeToName.TryGetValue(type, out retVal);
                return retVal;
            }
 
            static internal Type GetBaseType(XmlQualifiedName baseContractName)
            {
                Type retVal = null;
                nameToType.TryGetValue(baseContractName, out retVal);
                return retVal;
            }
 
            internal EnumDataContractCriticalHelper()
            {
                IsValueType = true;
            }
 
            internal EnumDataContractCriticalHelper(Type type)
                : base(type)
            {
                this.StableName = DataContract.GetStableName(type, out hasDataContract);
                Type baseType = Enum.GetUnderlyingType(type);
                baseContractName = GetBaseContractName(baseType);
                ImportBaseType(baseType);
                IsFlags = type.IsDefined(Globals.TypeOfFlagsAttribute, false);
                ImportDataMembers();
 
                XmlDictionary dictionary = new XmlDictionary(2 + Members.Count);
                Name = dictionary.Add(StableName.Name);
                Namespace = dictionary.Add(StableName.Namespace);
                childElementNames = new XmlDictionaryString[Members.Count];
                for (int i = 0; i < Members.Count; i++)
                    childElementNames[i] = dictionary.Add(Members[i].Name);
 
                DataContractAttribute dataContractAttribute;
                if (TryGetDCAttribute(type, out dataContractAttribute))
                {
                    if (dataContractAttribute.IsReference)
                    {
                        DataContract.ThrowInvalidDataContractException(
                                SR.GetString(SR.EnumTypeCannotHaveIsReference,
                                    DataContract.GetClrTypeFullName(type),
                                    dataContractAttribute.IsReference,
                                    false),
                                type);
                    }
                }
            }
 
            internal XmlQualifiedName BaseContractName
            {
                get
                {
                    return baseContractName;
                }
                set
                {
                    baseContractName = value;
                    Type baseType = GetBaseType(baseContractName);
                    if (baseType == null)
                        ThrowInvalidDataContractException(SR.GetString(SR.InvalidEnumBaseType, value.Name, value.Namespace, StableName.Name, StableName.Namespace));
                    ImportBaseType(baseType);
                }
            }
 
            internal List<DataMember> Members
            {
                get { return members; }
                set { members = value; }
            }
 
            internal List<long> Values
            {
                get { return values; }
                set { values = value; }
            }
 
            internal bool IsFlags
            {
                get { return isFlags; }
                set { isFlags = value; }
            }
 
            internal bool IsULong
            {
                get { return isULong; }
            }
 
            internal XmlDictionaryString[] ChildElementNames
            {
                get { return childElementNames; }
            }
 
            void ImportBaseType(Type baseType)
            {
                isULong = (baseType == Globals.TypeOfULong);
            }
 
            void ImportDataMembers()
            {
                Type type = this.UnderlyingType;
                FieldInfo[] fields = type.GetFields(BindingFlags.Static | BindingFlags.Public);
                Dictionary<string, DataMember> memberValuesTable = new Dictionary<string, DataMember>();
                List<DataMember> tempMembers = new List<DataMember>(fields.Length);
                List<long> tempValues = new List<long>(fields.Length);
 
                for (int i = 0; i < fields.Length; i++)
                {
                    FieldInfo field = fields[i];
                    bool enumMemberValid = false;
                    if (hasDataContract)
                    {
                        object[] memberAttributes = field.GetCustomAttributes(Globals.TypeOfEnumMemberAttribute, false);
                        if (memberAttributes != null && memberAttributes.Length > 0)
                        {
                            if (memberAttributes.Length > 1)
                                ThrowInvalidDataContractException(SR.GetString(SR.TooManyEnumMembers, DataContract.GetClrTypeFullName(field.DeclaringType), field.Name));
                            EnumMemberAttribute memberAttribute = (EnumMemberAttribute)memberAttributes[0];
 
                            DataMember memberContract = new DataMember(field);
                            if (memberAttribute.IsValueSetExplicitly)
                            {
                                if (memberAttribute.Value == null || memberAttribute.Value.Length == 0)
                                    ThrowInvalidDataContractException(SR.GetString(SR.InvalidEnumMemberValue, field.Name, DataContract.GetClrTypeFullName(type)));
                                memberContract.Name = memberAttribute.Value;
                            }
                            else
                                memberContract.Name = field.Name;
                            ClassDataContract.CheckAndAddMember(tempMembers, memberContract, memberValuesTable);
                            enumMemberValid = true;
                        }
 
                        object[] dataMemberAttributes = field.GetCustomAttributes(Globals.TypeOfDataMemberAttribute, false);
                        if (dataMemberAttributes != null && dataMemberAttributes.Length > 0)
                            ThrowInvalidDataContractException(SR.GetString(SR.DataMemberOnEnumField, DataContract.GetClrTypeFullName(field.DeclaringType), field.Name));
                    }
                    else
                    {
                        if (!field.IsNotSerialized)
                        {
                            DataMember memberContract = new DataMember(field);
                            memberContract.Name = field.Name;
                            ClassDataContract.CheckAndAddMember(tempMembers, memberContract, memberValuesTable);
                            enumMemberValid = true;
                        }
                    }
 
                    if (enumMemberValid)
                    {
                        object enumValue = field.GetValue(null);
                        if (isULong)
                            tempValues.Add((long)((IConvertible)enumValue).ToUInt64(null));
                        else
                            tempValues.Add(((IConvertible)enumValue).ToInt64(null));
                    }
                }
 
                Thread.MemoryBarrier();
                members = tempMembers;
                values = tempValues;
            }
        }
 
        internal void WriteEnumValue(XmlWriterDelegator writer, object value)
        {
            long longValue = IsULong ? (long)((IConvertible)value).ToUInt64(null) : ((IConvertible)value).ToInt64(null);
            for (int i = 0; i < Values.Count; i++)
            {
                if (longValue == Values[i])
                {
                    writer.WriteString(ChildElementNames[i].Value);
                    return;
                }
            }
            if (IsFlags)
            {
                int zeroIndex = -1;
                bool noneWritten = true;
                for (int i = 0; i < Values.Count; i++)
                {
                    long current = Values[i];
                    if (current == 0)
                    {
                        zeroIndex = i;
                        continue;
                    }
                    if (longValue == 0)
                        break;
                    if ((current & longValue) == current)
                    {
                        if (noneWritten)
                            noneWritten = false;
                        else
                            writer.WriteString(DictionaryGlobals.Space.Value);
 
                        writer.WriteString(ChildElementNames[i].Value);
                        longValue &= ~current;
                    }
                }
                // enforce that enum value was completely parsed
                if (longValue != 0)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(XmlObjectSerializer.CreateSerializationException(SR.GetString(SR.InvalidEnumValueOnWrite, value, DataContract.GetClrTypeFullName(UnderlyingType))));
 
                if (noneWritten && zeroIndex >= 0)
                    writer.WriteString(ChildElementNames[zeroIndex].Value);
            }
            else
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(XmlObjectSerializer.CreateSerializationException(SR.GetString(SR.InvalidEnumValueOnWrite, value, DataContract.GetClrTypeFullName(UnderlyingType))));
        }
 
        internal object ReadEnumValue(XmlReaderDelegator reader)
        {
            string stringValue = reader.ReadElementContentAsString();
            long longValue = 0;
            int i = 0;
            if (IsFlags)
            {
                // Skip initial spaces
                for (; i < stringValue.Length; i++)
                    if (stringValue[i] != ' ')
                        break;
 
                // Read space-delimited values
                int startIndex = i;
                int count = 0;
                for (; i < stringValue.Length; i++)
                {
                    if (stringValue[i] == ' ')
                    {
                        count = i - startIndex;
                        if (count > 0)
                            longValue |= ReadEnumValue(stringValue, startIndex, count);
                        for (++i; i < stringValue.Length; i++)
                            if (stringValue[i] != ' ')
                                break;
                        startIndex = i;
                        if (i == stringValue.Length)
                            break;
                    }
                }
                count = i - startIndex;
                if (count > 0)
                    longValue |= ReadEnumValue(stringValue, startIndex, count);
            }
            else
            {
                if (stringValue.Length == 0)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(XmlObjectSerializer.CreateSerializationException(SR.GetString(SR.InvalidEnumValueOnRead, stringValue, DataContract.GetClrTypeFullName(UnderlyingType))));
                longValue = ReadEnumValue(stringValue, 0, stringValue.Length);
            }
 
            if (IsULong)
                return Enum.ToObject(UnderlyingType, (ulong)longValue);
            return Enum.ToObject(UnderlyingType, longValue);
        }
 
        long ReadEnumValue(string value, int index, int count)
        {
            for (int i = 0; i < Members.Count; i++)
            {
                string memberName = Members[i].Name;
                if (memberName.Length == count && String.CompareOrdinal(value, index, memberName, 0, count) == 0)
                {
                    return Values[i];
                }
            }
            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(XmlObjectSerializer.CreateSerializationException(SR.GetString(SR.InvalidEnumValueOnRead, value.Substring(index, count), DataContract.GetClrTypeFullName(UnderlyingType))));
        }
 
        internal string GetStringFromEnumValue(long value)
        {
            if (IsULong)
                return XmlConvert.ToString((ulong)value);
            else
                return XmlConvert.ToString(value);
        }
 
        internal long GetEnumValueFromString(string value)
        {
            if (IsULong)
                return (long)XmlConverter.ToUInt64(value);
            else
                return XmlConverter.ToInt64(value);
        }
 
        internal override bool Equals(object other, Dictionary<DataContractPairKey, object> checkedContracts)
        {
            if (IsEqualOrChecked(other, checkedContracts))
                return true;
 
            if (base.Equals(other, null))
            {
                EnumDataContract dataContract = other as EnumDataContract;
                if (dataContract != null)
                {
                    if (Members.Count != dataContract.Members.Count || Values.Count != dataContract.Values.Count)
                        return false;
                    string[] memberNames1 = new string[Members.Count], memberNames2 = new string[Members.Count];
                    for (int i = 0; i < Members.Count; i++)
                    {
                        memberNames1[i] = Members[i].Name;
                        memberNames2[i] = dataContract.Members[i].Name;
                    }
                    Array.Sort(memberNames1);
                    Array.Sort(memberNames2);
                    for (int i = 0; i < Members.Count; i++)
                    {
                        if (memberNames1[i] != memberNames2[i])
                            return false;
                    }
 
                    return (IsFlags == dataContract.IsFlags);
                }
            }
            return false;
        }
 
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
 
        public override void WriteXmlValue(XmlWriterDelegator xmlWriter, object obj, XmlObjectSerializerWriteContext context)
        {
            WriteEnumValue(xmlWriter, obj);
        }
 
        public override object ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)
        {
            object obj = ReadEnumValue(xmlReader);
            if (context != null)
                context.AddNewObject(obj);
            return obj;
        }
 
    }
}