e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
510 lines
21 KiB
C#
510 lines
21 KiB
C#
//-----------------------------------------------------------------------------
|
|
// 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.")]
|
|
#if !NO_SECURITY_ATTRIBUTES
|
|
[SecurityCritical(SecurityCriticalScope.Everything)]
|
|
#endif
|
|
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;
|
|
}
|
|
|
|
}
|
|
}
|