#if !NO_RUNTIME
using System;
using System.Collections;
using System.Text;
using ProtoBuf.Serializers;
#if FEAT_IKVM
using Type = IKVM.Reflection.Type;
using IKVM.Reflection;
#if FEAT_COMPILER
using IKVM.Reflection.Emit;
#endif
#else
using System.Reflection;
#if FEAT_COMPILER
using System.Reflection.Emit;
#endif
#endif
namespace ProtoBuf.Meta
{
///
/// Represents a type at runtime for use with protobuf, allowing the field mappings (etc) to be defined
///
public class MetaType : ISerializerProxy
{
internal sealed class Comparer : IComparer
#if !NO_GENERICS
, System.Collections.Generic.IComparer
#endif
{
public static readonly Comparer Default = new Comparer();
public int Compare(object x, object y)
{
return Compare(x as MetaType, y as MetaType);
}
public int Compare(MetaType x, MetaType y)
{
if (ReferenceEquals(x, y)) return 0;
if (x == null) return -1;
if (y == null) return 1;
#if FX11
return string.Compare(x.GetSchemaTypeName(), y.GetSchemaTypeName());
#else
return string.Compare(x.GetSchemaTypeName(), y.GetSchemaTypeName(), StringComparison.Ordinal);
#endif
}
}
///
/// Get the name of the type being represented
///
public override string ToString()
{
return type.ToString();
}
IProtoSerializer ISerializerProxy.Serializer { get { return Serializer; } }
private MetaType baseType;
///
/// Gets the base-type for this type
///
public MetaType BaseType {
get { return baseType; }
}
internal TypeModel Model { get { return model; } }
///
/// When used to compile a model, should public serialization/deserialzation methods
/// be included for this type?
///
public bool IncludeSerializerMethod
{ // negated to minimize common-case / initializer
get { return !HasFlag(OPTIONS_PrivateOnApi); }
set { SetFlag(OPTIONS_PrivateOnApi, !value, true); }
}
///
/// Should this type be treated as a reference by default?
///
public bool AsReferenceDefault
{
get { return HasFlag(OPTIONS_AsReferenceDefault); }
set { SetFlag(OPTIONS_AsReferenceDefault, value, true); }
}
private BasicList subTypes;
private bool IsValidSubType(Type subType)
{
#if WINRT || COREFX
return typeInfo.IsAssignableFrom(subType.GetTypeInfo());
#else
return type.IsAssignableFrom(subType);
#endif
}
///
/// Adds a known sub-type to the inheritance model
///
public MetaType AddSubType(int fieldNumber, Type derivedType)
{
return AddSubType(fieldNumber, derivedType, DataFormat.Default);
}
///
/// Adds a known sub-type to the inheritance model
///
public MetaType AddSubType(int fieldNumber, Type derivedType, DataFormat dataFormat)
{
if (derivedType == null) throw new ArgumentNullException("derivedType");
if (fieldNumber < 1) throw new ArgumentOutOfRangeException("fieldNumber");
#if WINRT || COREFX || COREFX
if (!(typeInfo.IsClass || typeInfo.IsInterface) || typeInfo.IsSealed) {
#else
if (!(type.IsClass || type.IsInterface) || type.IsSealed) {
#endif
throw new InvalidOperationException("Sub-types can only be added to non-sealed classes");
}
if (!IsValidSubType(derivedType))
{
throw new ArgumentException(derivedType.Name + " is not a valid sub-type of " + type.Name, "derivedType");
}
MetaType derivedMeta = model[derivedType];
ThrowIfFrozen();
derivedMeta.ThrowIfFrozen();
SubType subType = new SubType(fieldNumber, derivedMeta, dataFormat);
ThrowIfFrozen();
derivedMeta.SetBaseType(this); // includes ThrowIfFrozen
if (subTypes == null) subTypes = new BasicList();
subTypes.Add(subType);
return this;
}
#if WINRT || COREFX
internal static readonly TypeInfo ienumerable = typeof(IEnumerable).GetTypeInfo();
#else
internal static readonly System.Type ienumerable = typeof(IEnumerable);
#endif
private void SetBaseType(MetaType baseType)
{
if (baseType == null) throw new ArgumentNullException("baseType");
if (this.baseType == baseType) return;
if (this.baseType != null) throw new InvalidOperationException("A type can only participate in one inheritance hierarchy");
MetaType type = baseType;
while (type != null)
{
if (ReferenceEquals(type, this)) throw new InvalidOperationException("Cyclic inheritance is not allowed");
type = type.baseType;
}
this.baseType = baseType;
}
private CallbackSet callbacks;
///
/// Indicates whether the current type has defined callbacks
///
public bool HasCallbacks
{
get { return callbacks != null && callbacks.NonTrivial; }
}
///
/// Indicates whether the current type has defined subtypes
///
public bool HasSubtypes
{
get { return subTypes != null && subTypes.Count != 0; }
}
///
/// Returns the set of callbacks defined for this type
///
public CallbackSet Callbacks
{
get
{
if (callbacks == null) callbacks = new CallbackSet(this);
return callbacks;
}
}
private bool IsValueType
{
get
{
#if WINRT || COREFX
return typeInfo.IsValueType;
#else
return type.IsValueType;
#endif
}
}
///
/// Assigns the callbacks to use during serialiation/deserialization.
///
/// The method (or null) called before serialization begins.
/// The method (or null) called when serialization is complete.
/// The method (or null) called before deserialization begins (or when a new instance is created during deserialization).
/// The method (or null) called when deserialization is complete.
/// The set of callbacks.
public MetaType SetCallbacks(MethodInfo beforeSerialize, MethodInfo afterSerialize, MethodInfo beforeDeserialize, MethodInfo afterDeserialize)
{
CallbackSet callbacks = Callbacks;
callbacks.BeforeSerialize = beforeSerialize;
callbacks.AfterSerialize = afterSerialize;
callbacks.BeforeDeserialize = beforeDeserialize;
callbacks.AfterDeserialize = afterDeserialize;
return this;
}
///
/// Assigns the callbacks to use during serialiation/deserialization.
///
/// The name of the method (or null) called before serialization begins.
/// The name of the method (or null) called when serialization is complete.
/// The name of the method (or null) called before deserialization begins (or when a new instance is created during deserialization).
/// The name of the method (or null) called when deserialization is complete.
/// The set of callbacks.
public MetaType SetCallbacks(string beforeSerialize, string afterSerialize, string beforeDeserialize, string afterDeserialize)
{
if (IsValueType) throw new InvalidOperationException();
CallbackSet callbacks = Callbacks;
callbacks.BeforeSerialize = ResolveMethod(beforeSerialize, true);
callbacks.AfterSerialize = ResolveMethod(afterSerialize, true);
callbacks.BeforeDeserialize = ResolveMethod(beforeDeserialize, true);
callbacks.AfterDeserialize = ResolveMethod(afterDeserialize, true);
return this;
}
internal string GetSchemaTypeName()
{
if (surrogate != null) return model[surrogate].GetSchemaTypeName();
if (!Helpers.IsNullOrEmpty(name)) return name;
string typeName = type.Name;
#if !NO_GENERICS
if (type
#if WINRT || COREFX
.GetTypeInfo()
#endif
.IsGenericType)
{
StringBuilder sb = new StringBuilder(typeName);
int split = typeName.IndexOf('`');
if (split >= 0) sb.Length = split;
foreach (Type arg in type
#if WINRT || COREFX
.GetTypeInfo().GenericTypeArguments
#else
.GetGenericArguments()
#endif
)
{
sb.Append('_');
Type tmp = arg;
int key = model.GetKey(ref tmp);
MetaType mt;
if (key >= 0 && (mt = model[tmp]) != null && mt.surrogate == null) // <=== need to exclude surrogate to avoid chance of infinite loop
{
sb.Append(mt.GetSchemaTypeName());
}
else
{
sb.Append(tmp.Name);
}
}
return sb.ToString();
}
#endif
return typeName;
}
private string name;
///
/// Gets or sets the name of this contract.
///
public string Name
{
get
{
return name;
}
set
{
ThrowIfFrozen();
name = value;
}
}
private MethodInfo factory;
///
/// Designate a factory-method to use to create instances of this type
///
public MetaType SetFactory(MethodInfo factory)
{
model.VerifyFactory(factory, type);
ThrowIfFrozen();
this.factory = factory;
return this;
}
///
/// Designate a factory-method to use to create instances of this type
///
public MetaType SetFactory(string factory)
{
return SetFactory(ResolveMethod(factory, false));
}
private MethodInfo ResolveMethod(string name, bool instance)
{
if (Helpers.IsNullOrEmpty(name)) return null;
#if WINRT || COREFX
return instance ? Helpers.GetInstanceMethod(typeInfo, name) : Helpers.GetStaticMethod(typeInfo, name);
#else
return instance ? Helpers.GetInstanceMethod(type, name) : Helpers.GetStaticMethod(type, name);
#endif
}
private readonly RuntimeTypeModel model;
internal static Exception InbuiltType(Type type)
{
return new ArgumentException("Data of this type has inbuilt behaviour, and cannot be added to a model in this way: " + type.FullName);
}
internal MetaType(RuntimeTypeModel model, Type type, MethodInfo factory)
{
this.factory = factory;
if (model == null) throw new ArgumentNullException("model");
if (type == null) throw new ArgumentNullException("type");
IProtoSerializer coreSerializer = model.TryGetBasicTypeSerializer(type);
if (coreSerializer != null)
{
throw InbuiltType(type);
}
this.type = type;
#if WINRT || COREFX
this.typeInfo = type.GetTypeInfo();
#endif
this.model = model;
if (Helpers.IsEnum(type))
{
#if WINRT || COREFX
EnumPassthru = typeInfo.IsDefined(typeof(FlagsAttribute), false);
#else
EnumPassthru = type.IsDefined(model.MapType(typeof(FlagsAttribute)), false);
#endif
}
}
#if WINRT || COREFX
private readonly TypeInfo typeInfo;
#endif
///
/// Throws an exception if the type has been made immutable
///
protected internal void ThrowIfFrozen()
{
if ((flags & OPTIONS_Frozen)!=0) throw new InvalidOperationException("The type cannot be changed once a serializer has been generated for " + type.FullName);
}
//internal void Freeze() { flags |= OPTIONS_Frozen; }
private readonly Type type;
///
/// The runtime type that the meta-type represents
///
public Type Type { get { return type; } }
private IProtoTypeSerializer serializer;
internal IProtoTypeSerializer Serializer {
get {
if (serializer == null)
{
int opaqueToken = 0;
try
{
model.TakeLock(ref opaqueToken);
if (serializer == null)
{ // double-check, but our main purpse with this lock is to ensure thread-safety with
// serializers needing to wait until another thread has finished adding the properties
SetFlag(OPTIONS_Frozen, true, false);
serializer = BuildSerializer();
#if FEAT_COMPILER && !FX11
if (model.AutoCompile) CompileInPlace();
#endif
}
}
finally
{
model.ReleaseLock(opaqueToken);
}
}
return serializer;
}
}
internal bool IsList
{
get
{
Type itemType = IgnoreListHandling ? null : TypeModel.GetListItemType(model, type);
return itemType != null;
}
}
private IProtoTypeSerializer BuildSerializer()
{
if (Helpers.IsEnum(type))
{
return new TagDecorator(ProtoBuf.Serializer.ListItemTag, WireType.Variant, false, new EnumSerializer(type, GetEnumMap()));
}
Type itemType = IgnoreListHandling ? null : TypeModel.GetListItemType(model, type);
if (itemType != null)
{
if(surrogate != null)
{
throw new ArgumentException("Repeated data (a list, collection, etc) has inbuilt behaviour and cannot use a surrogate");
}
if(subTypes != null && subTypes.Count != 0)
{
throw new ArgumentException("Repeated data (a list, collection, etc) has inbuilt behaviour and cannot be subclassed");
}
Type defaultType = null;
ResolveListTypes(model, type, ref itemType, ref defaultType);
ValueMember fakeMember = new ValueMember(model, ProtoBuf.Serializer.ListItemTag, type, itemType, defaultType, DataFormat.Default);
return new TypeSerializer(model, type, new int[] { ProtoBuf.Serializer.ListItemTag }, new IProtoSerializer[] { fakeMember.Serializer }, null, true, true, null, constructType, factory);
}
if (surrogate != null)
{
MetaType mt = model[surrogate], mtBase;
while ((mtBase = mt.baseType) != null) { mt = mtBase; }
return new SurrogateSerializer(model, type, surrogate, mt.Serializer);
}
if (IsAutoTuple)
{
MemberInfo[] mapping;
ConstructorInfo ctor = ResolveTupleConstructor(type, out mapping);
if(ctor == null) throw new InvalidOperationException();
return new TupleSerializer(model, ctor, mapping);
}
fields.Trim();
int fieldCount = fields.Count;
int subTypeCount = subTypes == null ? 0 : subTypes.Count;
int[] fieldNumbers = new int[fieldCount + subTypeCount];
IProtoSerializer[] serializers = new IProtoSerializer[fieldCount + subTypeCount];
int i = 0;
if (subTypeCount != 0)
{
foreach (SubType subType in subTypes)
{
#if WINRT || COREFX
if (!subType.DerivedType.IgnoreListHandling && ienumerable.IsAssignableFrom(subType.DerivedType.Type.GetTypeInfo()))
#else
if (!subType.DerivedType.IgnoreListHandling && model.MapType(ienumerable).IsAssignableFrom(subType.DerivedType.Type))
#endif
{
throw new ArgumentException("Repeated data (a list, collection, etc) has inbuilt behaviour and cannot be used as a subclass");
}
fieldNumbers[i] = subType.FieldNumber;
serializers[i++] = subType.Serializer;
}
}
if (fieldCount != 0)
{
foreach (ValueMember member in fields)
{
fieldNumbers[i] = member.FieldNumber;
serializers[i++] = member.Serializer;
}
}
BasicList baseCtorCallbacks = null;
MetaType tmp = BaseType;
while (tmp != null)
{
MethodInfo method = tmp.HasCallbacks ? tmp.Callbacks.BeforeDeserialize : null;
if (method != null)
{
if (baseCtorCallbacks == null) baseCtorCallbacks = new BasicList();
baseCtorCallbacks.Add(method);
}
tmp = tmp.BaseType;
}
MethodInfo[] arr = null;
if (baseCtorCallbacks != null)
{
arr = new MethodInfo[baseCtorCallbacks.Count];
baseCtorCallbacks.CopyTo(arr, 0);
Array.Reverse(arr);
}
return new TypeSerializer(model, type, fieldNumbers, serializers, arr, baseType == null, UseConstructor, callbacks, constructType, factory);
}
[Flags]
internal enum AttributeFamily
{
None = 0, ProtoBuf = 1, DataContractSerialier = 2, XmlSerializer = 4, AutoTuple = 8
}
static Type GetBaseType(MetaType type)
{
#if WINRT || COREFX
return type.typeInfo.BaseType;
#else
return type.type.BaseType;
#endif
}
internal static bool GetAsReferenceDefault(RuntimeTypeModel model, Type type)
{
if (type == null) throw new ArgumentNullException("type");
if (Helpers.IsEnum(type)) return false; // never as-ref
AttributeMap[] typeAttribs = AttributeMap.Create(model, type, false);
for (int i = 0; i < typeAttribs.Length; i++)
{
if (typeAttribs[i].AttributeType.FullName == "ProtoBuf.ProtoContractAttribute")
{
object tmp;
if (typeAttribs[i].TryGet("AsReferenceDefault", out tmp)) return (bool)tmp;
}
}
return false;
}
internal void ApplyDefaultBehaviour()
{
Type baseType = GetBaseType(this);
if (baseType != null && model.FindWithoutAdd(baseType) == null
&& GetContractFamily(model, baseType, null) != MetaType.AttributeFamily.None)
{
model.FindOrAddAuto(baseType, true, false, false);
}
AttributeMap[] typeAttribs = AttributeMap.Create(model, type, false);
AttributeFamily family = GetContractFamily(model, type, typeAttribs);
if(family == AttributeFamily.AutoTuple)
{
SetFlag(OPTIONS_AutoTuple, true, true);
}
bool isEnum = !EnumPassthru && Helpers.IsEnum(type);
if(family == AttributeFamily.None && !isEnum) return; // and you'd like me to do what, exactly?
BasicList partialIgnores = null, partialMembers = null;
int dataMemberOffset = 0, implicitFirstTag = 1;
bool inferTagByName = model.InferTagFromNameDefault;
ImplicitFields implicitMode = ImplicitFields.None;
string name = null;
for (int i = 0; i < typeAttribs.Length; i++)
{
AttributeMap item = (AttributeMap)typeAttribs[i];
object tmp;
string fullAttributeTypeName = item.AttributeType.FullName;
if (!isEnum && fullAttributeTypeName == "ProtoBuf.ProtoIncludeAttribute")
{
int tag = 0;
if (item.TryGet("tag", out tmp)) tag = (int)tmp;
DataFormat dataFormat = DataFormat.Default;
if(item.TryGet("DataFormat", out tmp))
{
dataFormat = (DataFormat)(int) tmp;
}
Type knownType = null;
try
{
if (item.TryGet("knownTypeName", out tmp)) knownType = model.GetType((string)tmp, type
#if WINRT || COREFX
.GetTypeInfo()
#endif
.Assembly);
else if (item.TryGet("knownType", out tmp)) knownType = (Type)tmp;
}
catch (Exception ex)
{
throw new InvalidOperationException("Unable to resolve sub-type of: " + type.FullName, ex);
}
if (knownType == null)
{
throw new InvalidOperationException("Unable to resolve sub-type of: " + type.FullName);
}
if(IsValidSubType(knownType)) AddSubType(tag, knownType, dataFormat);
}
if (fullAttributeTypeName == "ProtoBuf.ProtoPartialIgnoreAttribute")
{
if (item.TryGet("MemberName", out tmp) && tmp != null)
{
if (partialIgnores == null) partialIgnores = new BasicList();
partialIgnores.Add((string)tmp);
}
}
if (!isEnum && fullAttributeTypeName == "ProtoBuf.ProtoPartialMemberAttribute")
{
if (partialMembers == null) partialMembers = new BasicList();
partialMembers.Add(item);
}
if (fullAttributeTypeName == "ProtoBuf.ProtoContractAttribute")
{
if (item.TryGet("Name", out tmp)) name = (string) tmp;
if (Helpers.IsEnum(type)) // note this is subtly different to isEnum; want to do this even if [Flags]
{
#if !FEAT_IKVM
// IKVM can't access EnumPassthruHasValue, but conveniently, InferTagFromName will only be returned if set via ctor or property
if (item.TryGet("EnumPassthruHasValue", false, out tmp) && (bool)tmp)
#endif
{
if (item.TryGet("EnumPassthru", out tmp))
{
EnumPassthru = (bool)tmp;
if (EnumPassthru) isEnum = false; // no longer treated as an enum
}
}
}
else
{
if (item.TryGet("DataMemberOffset", out tmp)) dataMemberOffset = (int) tmp;
#if !FEAT_IKVM
// IKVM can't access InferTagFromNameHasValue, but conveniently, InferTagFromName will only be returned if set via ctor or property
if (item.TryGet("InferTagFromNameHasValue", false, out tmp) && (bool) tmp)
#endif
{
if (item.TryGet("InferTagFromName", out tmp)) inferTagByName = (bool) tmp;
}
if (item.TryGet("ImplicitFields", out tmp) && tmp != null)
{
implicitMode = (ImplicitFields) (int) tmp; // note that this uses the bizarre unboxing rules of enums/underlying-types
}
if (item.TryGet("SkipConstructor", out tmp)) UseConstructor = !(bool) tmp;
if (item.TryGet("IgnoreListHandling", out tmp)) IgnoreListHandling = (bool) tmp;
if (item.TryGet("AsReferenceDefault", out tmp)) AsReferenceDefault = (bool) tmp;
if (item.TryGet("ImplicitFirstTag", out tmp) && (int) tmp > 0) implicitFirstTag = (int) tmp;
}
}
if (fullAttributeTypeName == "System.Runtime.Serialization.DataContractAttribute")
{
if (name == null && item.TryGet("Name", out tmp)) name = (string)tmp;
}
if (fullAttributeTypeName == "System.Xml.Serialization.XmlTypeAttribute")
{
if (name == null && item.TryGet("TypeName", out tmp)) name = (string)tmp;
}
}
if (!Helpers.IsNullOrEmpty(name)) Name = name;
if (implicitMode != ImplicitFields.None)
{
family &= AttributeFamily.ProtoBuf; // with implicit fields, **only** proto attributes are important
}
MethodInfo[] callbacks = null;
BasicList members = new BasicList();
#if WINRT
System.Collections.Generic.IEnumerable foundList;
if(isEnum) {
foundList = type.GetRuntimeFields();
}
else
{
System.Collections.Generic.List list = new System.Collections.Generic.List();
foreach(PropertyInfo prop in type.GetRuntimeProperties()) {
MethodInfo getter = Helpers.GetGetMethod(prop, false, false);
if(getter != null && !getter.IsStatic) list.Add(prop);
}
foreach(FieldInfo fld in type.GetRuntimeFields()) if(fld.IsPublic && !fld.IsStatic) list.Add(fld);
foreach(MethodInfo mthd in type.GetRuntimeMethods()) if(mthd.IsPublic && !mthd.IsStatic) list.Add(mthd);
foundList = list;
}
#else
MemberInfo[] foundList = type.GetMembers(isEnum ? BindingFlags.Public | BindingFlags.Static
: BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
#endif
foreach (MemberInfo member in foundList)
{
if (member.DeclaringType != type) continue;
if (member.IsDefined(model.MapType(typeof(ProtoIgnoreAttribute)), true)) continue;
if (partialIgnores != null && partialIgnores.Contains(member.Name)) continue;
bool forced = false, isPublic, isField;
Type effectiveType;
PropertyInfo property;
FieldInfo field;
MethodInfo method;
if((property = member as PropertyInfo) != null)
{
if (isEnum) continue; // wasn't expecting any props!
effectiveType = property.PropertyType;
isPublic = Helpers.GetGetMethod(property, false, false) != null;
isField = false;
ApplyDefaultBehaviour_AddMembers(model, family, isEnum, partialMembers, dataMemberOffset, inferTagByName, implicitMode, members, member, ref forced, isPublic, isField, ref effectiveType);
} else if ((field = member as FieldInfo) != null)
{
effectiveType = field.FieldType;
isPublic = field.IsPublic;
isField = true;
if (isEnum && !field.IsStatic)
{ // only care about static things on enums; WinRT has a __value instance field!
continue;
}
ApplyDefaultBehaviour_AddMembers(model, family, isEnum, partialMembers, dataMemberOffset, inferTagByName, implicitMode, members, member, ref forced, isPublic, isField, ref effectiveType);
} else if ((method = member as MethodInfo) != null)
{
if (isEnum) continue;
AttributeMap[] memberAttribs = AttributeMap.Create(model, method, false);
if (memberAttribs != null && memberAttribs.Length > 0)
{
CheckForCallback(method, memberAttribs, "ProtoBuf.ProtoBeforeSerializationAttribute", ref callbacks, 0);
CheckForCallback(method, memberAttribs, "ProtoBuf.ProtoAfterSerializationAttribute", ref callbacks, 1);
CheckForCallback(method, memberAttribs, "ProtoBuf.ProtoBeforeDeserializationAttribute", ref callbacks, 2);
CheckForCallback(method, memberAttribs, "ProtoBuf.ProtoAfterDeserializationAttribute", ref callbacks, 3);
CheckForCallback(method, memberAttribs, "System.Runtime.Serialization.OnSerializingAttribute", ref callbacks, 4);
CheckForCallback(method, memberAttribs, "System.Runtime.Serialization.OnSerializedAttribute", ref callbacks, 5);
CheckForCallback(method, memberAttribs, "System.Runtime.Serialization.OnDeserializingAttribute", ref callbacks, 6);
CheckForCallback(method, memberAttribs, "System.Runtime.Serialization.OnDeserializedAttribute", ref callbacks, 7);
}
}
}
ProtoMemberAttribute[] arr = new ProtoMemberAttribute[members.Count];
members.CopyTo(arr, 0);
if (inferTagByName || implicitMode != ImplicitFields.None)
{
Array.Sort(arr);
int nextTag = implicitFirstTag;
foreach (ProtoMemberAttribute normalizedAttribute in arr)
{
if (!normalizedAttribute.TagIsPinned) // if ProtoMember etc sets a tag, we'll trust it
{
normalizedAttribute.Rebase(nextTag++);
}
}
}
foreach (ProtoMemberAttribute normalizedAttribute in arr)
{
ValueMember vm = ApplyDefaultBehaviour(isEnum, normalizedAttribute);
if (vm != null)
{
Add(vm);
}
}
if (callbacks != null)
{
SetCallbacks(Coalesce(callbacks, 0, 4), Coalesce(callbacks, 1, 5),
Coalesce(callbacks, 2, 6), Coalesce(callbacks, 3, 7));
}
}
private static void ApplyDefaultBehaviour_AddMembers(TypeModel model, AttributeFamily family, bool isEnum, BasicList partialMembers, int dataMemberOffset, bool inferTagByName, ImplicitFields implicitMode, BasicList members, MemberInfo member, ref bool forced, bool isPublic, bool isField, ref Type effectiveType)
{
switch (implicitMode)
{
case ImplicitFields.AllFields:
if (isField) forced = true;
break;
case ImplicitFields.AllPublic:
if (isPublic) forced = true;
break;
}
// we just don't like delegate types ;p
#if WINRT || COREFX
if (effectiveType.GetTypeInfo().IsSubclassOf(typeof(Delegate))) effectiveType = null;
#else
if (effectiveType.IsSubclassOf(model.MapType(typeof(Delegate)))) effectiveType = null;
#endif
if (effectiveType != null)
{
ProtoMemberAttribute normalizedAttribute = NormalizeProtoMember(model, member, family, forced, isEnum, partialMembers, dataMemberOffset, inferTagByName);
if (normalizedAttribute != null) members.Add(normalizedAttribute);
}
}
static MethodInfo Coalesce(MethodInfo[] arr, int x, int y)
{
MethodInfo mi = arr[x];
if (mi == null) mi = arr[y];
return mi;
}
internal static AttributeFamily GetContractFamily(RuntimeTypeModel model, Type type, AttributeMap[] attributes)
{
AttributeFamily family = AttributeFamily.None;
if (attributes == null) attributes = AttributeMap.Create(model, type, false);
for (int i = 0; i < attributes.Length; i++)
{
switch (attributes[i].AttributeType.FullName)
{
case "ProtoBuf.ProtoContractAttribute":
bool tmp = false;
GetFieldBoolean(ref tmp, attributes[i], "UseProtoMembersOnly");
if (tmp) return AttributeFamily.ProtoBuf;
family |= AttributeFamily.ProtoBuf;
break;
case "System.Xml.Serialization.XmlTypeAttribute":
if (!model.AutoAddProtoContractTypesOnly)
{
family |= AttributeFamily.XmlSerializer;
}
break;
case "System.Runtime.Serialization.DataContractAttribute":
if (!model.AutoAddProtoContractTypesOnly)
{
family |= AttributeFamily.DataContractSerialier;
}
break;
}
}
if(family == AttributeFamily.None)
{ // check for obvious tuples
MemberInfo[] mapping;
if(ResolveTupleConstructor(type, out mapping) != null)
{
family |= AttributeFamily.AutoTuple;
}
}
return family;
}
internal static ConstructorInfo ResolveTupleConstructor(Type type, out MemberInfo[] mappedMembers)
{
mappedMembers = null;
if(type == null) throw new ArgumentNullException("type");
#if WINRT || COREFX
TypeInfo typeInfo = type.GetTypeInfo();
if (typeInfo.IsAbstract) return null; // as if!
ConstructorInfo[] ctors = Helpers.GetConstructors(typeInfo, false);
#else
if(type.IsAbstract) return null; // as if!
ConstructorInfo[] ctors = Helpers.GetConstructors(type, false);
#endif
// need to have an interesting constructor to bother even checking this stuff
if(ctors.Length == 0 || (ctors.Length == 1 && ctors[0].GetParameters().Length == 0)) return null;
MemberInfo[] fieldsPropsUnfiltered = Helpers.GetInstanceFieldsAndProperties(type, true);
BasicList memberList = new BasicList();
for (int i = 0; i < fieldsPropsUnfiltered.Length; i++)
{
PropertyInfo prop = fieldsPropsUnfiltered[i] as PropertyInfo;
if (prop != null)
{
if (!prop.CanRead) return null; // no use if can't read
if (prop.CanWrite && Helpers.GetSetMethod(prop, false, false) != null) return null; // don't allow a public set (need to allow non-public to handle Mono's KeyValuePair<,>)
memberList.Add(prop);
}
else
{
FieldInfo field = fieldsPropsUnfiltered[i] as FieldInfo;
if (field != null)
{
if (!field.IsInitOnly) return null; // all public fields must be readonly to be counted a tuple
memberList.Add(field);
}
}
}
if (memberList.Count == 0)
{
return null;
}
MemberInfo[] members = new MemberInfo[memberList.Count];
memberList.CopyTo(members, 0);
int[] mapping = new int[members.Length];
int found = 0;
ConstructorInfo result = null;
mappedMembers = new MemberInfo[mapping.Length];
for(int i = 0 ; i < ctors.Length ; i++)
{
ParameterInfo[] parameters = ctors[i].GetParameters();
if (parameters.Length != members.Length) continue;
// reset the mappings to test
for (int j = 0; j < mapping.Length; j++) mapping[j] = -1;
for(int j = 0 ; j < parameters.Length ; j++)
{
for(int k = 0 ; k < members.Length ; k++)
{
if (string.Compare(parameters[j].Name, members[k].Name, StringComparison.OrdinalIgnoreCase) != 0) continue;
Type memberType = Helpers.GetMemberType(members[k]);
if (memberType != parameters[j].ParameterType) continue;
mapping[j] = k;
}
}
// did we map all?
bool notMapped = false;
for (int j = 0; j < mapping.Length; j++)
{
if (mapping[j] < 0)
{
notMapped = true;
break;
}
mappedMembers[j] = members[mapping[j]];
}
if (notMapped) continue;
found++;
result = ctors[i];
}
return found == 1 ? result : null;
}
private static void CheckForCallback(MethodInfo method, AttributeMap[] attributes, string callbackTypeName, ref MethodInfo[] callbacks, int index)
{
for(int i = 0 ; i < attributes.Length ; i++)
{
if(attributes[i].AttributeType.FullName == callbackTypeName)
{
if (callbacks == null) { callbacks = new MethodInfo[8]; }
else if (callbacks[index] != null)
{
#if WINRT || FEAT_IKVM || COREFX
Type reflected = method.DeclaringType;
#else
Type reflected = method.ReflectedType;
#endif
throw new ProtoException("Duplicate " + callbackTypeName + " callbacks on " + reflected.FullName);
}
callbacks[index] = method;
}
}
}
private static bool HasFamily(AttributeFamily value, AttributeFamily required)
{
return (value & required) == required;
}
private static ProtoMemberAttribute NormalizeProtoMember(TypeModel model, MemberInfo member, AttributeFamily family, bool forced, bool isEnum, BasicList partialMembers, int dataMemberOffset, bool inferByTagName)
{
if (member == null || (family == AttributeFamily.None && !isEnum)) return null; // nix
int fieldNumber = int.MinValue, minAcceptFieldNumber = inferByTagName ? -1 : 1;
string name = null;
bool isPacked = false, ignore = false, done = false, isRequired = false, asReference = false, asReferenceHasValue = false, dynamicType = false, tagIsPinned = false, overwriteList = false;
DataFormat dataFormat = DataFormat.Default;
if (isEnum) forced = true;
AttributeMap[] attribs = AttributeMap.Create(model, member, true);
AttributeMap attrib;
if (isEnum)
{
attrib = GetAttribute(attribs, "ProtoBuf.ProtoIgnoreAttribute");
if (attrib != null)
{
ignore = true;
}
else
{
attrib = GetAttribute(attribs, "ProtoBuf.ProtoEnumAttribute");
#if WINRT || PORTABLE || CF || FX11 || COREFX
fieldNumber = Convert.ToInt32(((FieldInfo)member).GetValue(null));
#else
fieldNumber = Convert.ToInt32(((FieldInfo)member).GetRawConstantValue());
#endif
if (attrib != null)
{
GetFieldName(ref name, attrib, "Name");
#if !FEAT_IKVM // IKVM can't access HasValue, but conveniently, Value will only be returned if set via ctor or property
if ((bool)Helpers.GetInstanceMethod(attrib.AttributeType
#if WINRT || COREFX
.GetTypeInfo()
#endif
,"HasValue").Invoke(attrib.Target, null))
#endif
{
object tmp;
if(attrib.TryGet("Value", out tmp)) fieldNumber = (int)tmp;
}
}
}
done = true;
}
if (!ignore && !done) // always consider ProtoMember
{
attrib = GetAttribute(attribs, "ProtoBuf.ProtoMemberAttribute");
GetIgnore(ref ignore, attrib, attribs, "ProtoBuf.ProtoIgnoreAttribute");
if (!ignore && attrib != null)
{
GetFieldNumber(ref fieldNumber, attrib, "Tag");
GetFieldName(ref name, attrib, "Name");
GetFieldBoolean(ref isRequired, attrib, "IsRequired");
GetFieldBoolean(ref isPacked, attrib, "IsPacked");
GetFieldBoolean(ref overwriteList, attrib, "OverwriteList");
GetDataFormat(ref dataFormat, attrib, "DataFormat");
#if !FEAT_IKVM
// IKVM can't access AsReferenceHasValue, but conveniently, AsReference will only be returned if set via ctor or property
GetFieldBoolean(ref asReferenceHasValue, attrib, "AsReferenceHasValue", false);
if(asReferenceHasValue)
#endif
{
asReferenceHasValue = GetFieldBoolean(ref asReference, attrib, "AsReference", true);
}
GetFieldBoolean(ref dynamicType, attrib, "DynamicType");
done = tagIsPinned = fieldNumber > 0; // note minAcceptFieldNumber only applies to non-proto
}
if (!done && partialMembers != null)
{
foreach (AttributeMap ppma in partialMembers)
{
object tmp;
if(ppma.TryGet("MemberName", out tmp) && (string)tmp == member.Name)
{
GetFieldNumber(ref fieldNumber, ppma, "Tag");
GetFieldName(ref name, ppma, "Name");
GetFieldBoolean(ref isRequired, ppma, "IsRequired");
GetFieldBoolean(ref isPacked, ppma, "IsPacked");
GetFieldBoolean(ref overwriteList, attrib, "OverwriteList");
GetDataFormat(ref dataFormat, ppma, "DataFormat");
#if !FEAT_IKVM
// IKVM can't access AsReferenceHasValue, but conveniently, AsReference will only be returned if set via ctor or property
GetFieldBoolean(ref asReferenceHasValue, attrib, "AsReferenceHasValue", false);
if (asReferenceHasValue)
#endif
{
asReferenceHasValue = GetFieldBoolean(ref asReference, ppma, "AsReference", true);
}
GetFieldBoolean(ref dynamicType, ppma, "DynamicType");
if (done = tagIsPinned = fieldNumber > 0) break; // note minAcceptFieldNumber only applies to non-proto
}
}
}
}
if (!ignore && !done && HasFamily(family, AttributeFamily.DataContractSerialier))
{
attrib = GetAttribute(attribs, "System.Runtime.Serialization.DataMemberAttribute");
if (attrib != null)
{
GetFieldNumber(ref fieldNumber, attrib, "Order");
GetFieldName(ref name, attrib, "Name");
GetFieldBoolean(ref isRequired, attrib, "IsRequired");
done = fieldNumber >= minAcceptFieldNumber;
if (done) fieldNumber += dataMemberOffset; // dataMemberOffset only applies to DCS flags, to allow us to "bump" WCF by a notch
}
}
if (!ignore && !done && HasFamily(family, AttributeFamily.XmlSerializer))
{
attrib = GetAttribute(attribs, "System.Xml.Serialization.XmlElementAttribute");
if(attrib == null) attrib = GetAttribute(attribs, "System.Xml.Serialization.XmlArrayAttribute");
GetIgnore(ref ignore, attrib, attribs, "System.Xml.Serialization.XmlIgnoreAttribute");
if (attrib != null && !ignore)
{
GetFieldNumber(ref fieldNumber, attrib, "Order");
GetFieldName(ref name, attrib, "ElementName");
done = fieldNumber >= minAcceptFieldNumber;
}
}
if (!ignore && !done)
{
if (GetAttribute(attribs, "System.NonSerializedAttribute") != null) ignore = true;
}
if (ignore || (fieldNumber < minAcceptFieldNumber && !forced)) return null;
ProtoMemberAttribute result = new ProtoMemberAttribute(fieldNumber, forced || inferByTagName);
result.AsReference = asReference;
result.AsReferenceHasValue = asReferenceHasValue;
result.DataFormat = dataFormat;
result.DynamicType = dynamicType;
result.IsPacked = isPacked;
result.OverwriteList = overwriteList;
result.IsRequired = isRequired;
result.Name = Helpers.IsNullOrEmpty(name) ? member.Name : name;
result.Member = member;
result.TagIsPinned = tagIsPinned;
return result;
}
private ValueMember ApplyDefaultBehaviour(bool isEnum, ProtoMemberAttribute normalizedAttribute)
{
MemberInfo member;
if (normalizedAttribute == null || (member = normalizedAttribute.Member) == null) return null; // nix
Type effectiveType = Helpers.GetMemberType(member);
Type itemType = null;
Type defaultType = null;
// check for list types
ResolveListTypes(model, effectiveType, ref itemType, ref defaultType);
// but take it back if it is explicitly excluded
if(itemType != null)
{ // looks like a list, but double check for IgnoreListHandling
int idx = model.FindOrAddAuto(effectiveType, false, true, false);
if(idx >= 0 && model[effectiveType].IgnoreListHandling)
{
itemType = null;
defaultType = null;
}
}
AttributeMap[] attribs = AttributeMap.Create(model, member, true);
AttributeMap attrib;
object defaultValue = null;
// implicit zero default
if (model.UseImplicitZeroDefaults)
{
switch (Helpers.GetTypeCode(effectiveType))
{
case ProtoTypeCode.Boolean: defaultValue = false; break;
case ProtoTypeCode.Decimal: defaultValue = (decimal)0; break;
case ProtoTypeCode.Single: defaultValue = (float)0; break;
case ProtoTypeCode.Double: defaultValue = (double)0; break;
case ProtoTypeCode.Byte: defaultValue = (byte)0; break;
case ProtoTypeCode.Char: defaultValue = (char)0; break;
case ProtoTypeCode.Int16: defaultValue = (short)0; break;
case ProtoTypeCode.Int32: defaultValue = (int)0; break;
case ProtoTypeCode.Int64: defaultValue = (long)0; break;
case ProtoTypeCode.SByte: defaultValue = (sbyte)0; break;
case ProtoTypeCode.UInt16: defaultValue = (ushort)0; break;
case ProtoTypeCode.UInt32: defaultValue = (uint)0; break;
case ProtoTypeCode.UInt64: defaultValue = (ulong)0; break;
case ProtoTypeCode.TimeSpan: defaultValue = TimeSpan.Zero; break;
case ProtoTypeCode.Guid: defaultValue = Guid.Empty; break;
}
}
if ((attrib = GetAttribute(attribs, "System.ComponentModel.DefaultValueAttribute")) != null)
{
object tmp;
if(attrib.TryGet("Value", out tmp)) defaultValue = tmp;
}
ValueMember vm = ((isEnum || normalizedAttribute.Tag > 0))
? new ValueMember(model, type, normalizedAttribute.Tag, member, effectiveType, itemType, defaultType, normalizedAttribute.DataFormat, defaultValue)
: null;
if (vm != null)
{
#if WINRT || COREFX
TypeInfo finalType = typeInfo;
#else
Type finalType = type;
#endif
PropertyInfo prop = Helpers.GetProperty(finalType, member.Name + "Specified", true);
MethodInfo getMethod = Helpers.GetGetMethod(prop, true, true);
if (getMethod == null || getMethod.IsStatic) prop = null;
if (prop != null)
{
vm.SetSpecified(getMethod, Helpers.GetSetMethod(prop, true, true));
}
else
{
MethodInfo method = Helpers.GetInstanceMethod(finalType, "ShouldSerialize" + member.Name, Helpers.EmptyTypes);
if (method != null && method.ReturnType == model.MapType(typeof(bool)))
{
vm.SetSpecified(method, null);
}
}
if (!Helpers.IsNullOrEmpty(normalizedAttribute.Name)) vm.SetName(normalizedAttribute.Name);
vm.IsPacked = normalizedAttribute.IsPacked;
vm.IsRequired = normalizedAttribute.IsRequired;
vm.OverwriteList = normalizedAttribute.OverwriteList;
if (normalizedAttribute.AsReferenceHasValue)
{
vm.AsReference = normalizedAttribute.AsReference;
}
vm.DynamicType = normalizedAttribute.DynamicType;
}
return vm;
}
private static void GetDataFormat(ref DataFormat value, AttributeMap attrib, string memberName)
{
if ((attrib == null) || (value != DataFormat.Default)) return;
object obj;
if (attrib.TryGet(memberName, out obj) && obj != null) value = (DataFormat)obj;
}
private static void GetIgnore(ref bool ignore, AttributeMap attrib, AttributeMap[] attribs, string fullName)
{
if (ignore || attrib == null) return;
ignore = GetAttribute(attribs, fullName) != null;
return;
}
private static void GetFieldBoolean(ref bool value, AttributeMap attrib, string memberName)
{
GetFieldBoolean(ref value, attrib, memberName, true);
}
private static bool GetFieldBoolean(ref bool value, AttributeMap attrib, string memberName, bool publicOnly)
{
if (attrib == null) return false;
if (value) return true;
object obj;
if (attrib.TryGet(memberName, publicOnly, out obj) && obj != null)
{
value = (bool)obj;
return true;
}
return false;
}
private static void GetFieldNumber(ref int value, AttributeMap attrib, string memberName)
{
if (attrib == null || value > 0) return;
object obj;
if (attrib.TryGet(memberName, out obj) && obj != null) value = (int)obj;
}
private static void GetFieldName(ref string name, AttributeMap attrib, string memberName)
{
if (attrib == null || !Helpers.IsNullOrEmpty(name)) return;
object obj;
if (attrib.TryGet(memberName, out obj) && obj != null) name = (string)obj;
}
private static AttributeMap GetAttribute(AttributeMap[] attribs, string fullName)
{
for (int i = 0; i < attribs.Length; i++)
{
AttributeMap attrib = attribs[i];
if (attrib != null && attrib.AttributeType.FullName == fullName) return attrib;
}
return null;
}
///
/// Adds a member (by name) to the MetaType
///
public MetaType Add(int fieldNumber, string memberName)
{
AddField(fieldNumber, memberName, null, null, null);
return this;
}
///
/// Adds a member (by name) to the MetaType, returning the ValueMember rather than the fluent API.
/// This is otherwise identical to Add.
///
public ValueMember AddField(int fieldNumber, string memberName)
{
return AddField(fieldNumber, memberName, null, null, null);
}
///
/// Gets or sets whether the type should use a parameterless constructor (the default),
/// or whether the type should skip the constructor completely. This option is not supported
/// on compact-framework.
///
public bool UseConstructor
{ // negated to have defaults as flat zero
get { return !HasFlag(OPTIONS_SkipConstructor); }
set { SetFlag(OPTIONS_SkipConstructor, !value, true); }
}
///
/// The concrete type to create when a new instance of this type is needed; this may be useful when dealing
/// with dynamic proxies, or with interface-based APIs
///
public Type ConstructType
{
get { return constructType; }
set
{
ThrowIfFrozen();
constructType = value;
}
}
private Type constructType;
///
/// Adds a member (by name) to the MetaType
///
public MetaType Add(string memberName)
{
Add(GetNextFieldNumber(), memberName);
return this;
}
Type surrogate;
///
/// Performs serialization of this type via a surrogate; all
/// other serialization options are ignored and handled
/// by the surrogate's configuration.
///
public void SetSurrogate(Type surrogateType)
{
if (surrogateType == type) surrogateType = null;
if (surrogateType != null)
{
// note that BuildSerializer checks the **CURRENT TYPE** is OK to be surrogated
if (surrogateType != null && Helpers.IsAssignableFrom(model.MapType(typeof(IEnumerable)), surrogateType))
{
throw new ArgumentException("Repeated data (a list, collection, etc) has inbuilt behaviour and cannot be used as a surrogate");
}
}
ThrowIfFrozen();
this.surrogate = surrogateType;
// no point in offering chaining; no options are respected
}
internal MetaType GetSurrogateOrSelf()
{
if (surrogate != null) return model[surrogate];
return this;
}
internal MetaType GetSurrogateOrBaseOrSelf(bool deep) {
if(surrogate != null) return model[surrogate];
MetaType snapshot = this.baseType;
if (snapshot != null)
{
if (deep)
{
MetaType tmp;
do
{
tmp = snapshot;
snapshot = snapshot.baseType;
} while(snapshot != null);
return tmp;
}
return snapshot;
}
return this;
}
private int GetNextFieldNumber()
{
int maxField = 0;
foreach (ValueMember member in fields)
{
if (member.FieldNumber > maxField) maxField = member.FieldNumber;
}
if (subTypes != null)
{
foreach (SubType subType in subTypes)
{
if (subType.FieldNumber > maxField) maxField = subType.FieldNumber;
}
}
return maxField + 1;
}
///
/// Adds a set of members (by name) to the MetaType
///
public MetaType Add(params string[] memberNames)
{
if (memberNames == null) throw new ArgumentNullException("memberNames");
int next = GetNextFieldNumber();
for (int i = 0; i < memberNames.Length; i++)
{
Add(next++, memberNames[i]);
}
return this;
}
///
/// Adds a member (by name) to the MetaType
///
public MetaType Add(int fieldNumber, string memberName, object defaultValue)
{
AddField(fieldNumber, memberName, null, null, defaultValue);
return this;
}
///
/// Adds a member (by name) to the MetaType, including an itemType and defaultType for representing lists
///
public MetaType Add(int fieldNumber, string memberName, Type itemType, Type defaultType)
{
AddField(fieldNumber, memberName, itemType, defaultType, null);
return this;
}
///
/// Adds a member (by name) to the MetaType, including an itemType and defaultType for representing lists, returning the ValueMember rather than the fluent API.
/// This is otherwise identical to Add.
///
public ValueMember AddField(int fieldNumber, string memberName, Type itemType, Type defaultType)
{
return AddField(fieldNumber, memberName, itemType, defaultType, null);
}
private ValueMember AddField(int fieldNumber, string memberName, Type itemType, Type defaultType, object defaultValue)
{
MemberInfo mi = null;
#if WINRT
mi = Helpers.IsEnum(type) ? type.GetTypeInfo().GetDeclaredField(memberName) : Helpers.GetInstanceMember(type.GetTypeInfo(), memberName);
#else
MemberInfo[] members = type.GetMember(memberName, Helpers.IsEnum(type) ? BindingFlags.Static | BindingFlags.Public : BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if(members != null && members.Length == 1) mi = members[0];
#endif
if (mi == null) throw new ArgumentException("Unable to determine member: " + memberName, "memberName");
Type miType;
#if WINRT || PORTABLE || COREFX
PropertyInfo pi = mi as PropertyInfo;
if (pi == null)
{
FieldInfo fi = mi as FieldInfo;
if (fi == null)
{
throw new NotSupportedException(mi.GetType().Name);
}
else
{
miType = fi.FieldType;
}
}
else
{
miType = pi.PropertyType;
}
#else
switch (mi.MemberType)
{
case MemberTypes.Field:
miType = ((FieldInfo)mi).FieldType; break;
case MemberTypes.Property:
miType = ((PropertyInfo)mi).PropertyType; break;
default:
throw new NotSupportedException(mi.MemberType.ToString());
}
#endif
ResolveListTypes(model, miType, ref itemType, ref defaultType);
ValueMember newField = new ValueMember(model, type, fieldNumber, mi, miType, itemType, defaultType, DataFormat.Default, defaultValue);
Add(newField);
return newField;
}
internal static void ResolveListTypes(TypeModel model, Type type, ref Type itemType, ref Type defaultType)
{
if (type == null) return;
// handle arrays
if (type.IsArray)
{
if (type.GetArrayRank() != 1)
{
throw new NotSupportedException("Multi-dimensional arrays are not supported");
}
itemType = type.GetElementType();
if (itemType == model.MapType(typeof(byte)))
{
defaultType = itemType = null;
}
else
{
defaultType = type;
}
}
// handle lists
if (itemType == null) { itemType = TypeModel.GetListItemType(model, type); }
// check for nested data (not allowed)
if (itemType != null)
{
Type nestedItemType = null, nestedDefaultType = null;
ResolveListTypes(model, itemType, ref nestedItemType, ref nestedDefaultType);
if (nestedItemType != null)
{
throw TypeModel.CreateNestedListsNotSupported();
}
}
if (itemType != null && defaultType == null)
{
#if WINRT || COREFX
TypeInfo typeInfo = type.GetTypeInfo();
if (typeInfo.IsClass && !typeInfo.IsAbstract && Helpers.GetConstructor(typeInfo, Helpers.EmptyTypes, true) != null)
#else
if (type.IsClass && !type.IsAbstract && Helpers.GetConstructor(type, Helpers.EmptyTypes, true) != null)
#endif
{
defaultType = type;
}
if (defaultType == null)
{
#if WINRT || COREFX
if (typeInfo.IsInterface)
#else
if (type.IsInterface)
#endif
{
#if NO_GENERICS
defaultType = typeof(ArrayList);
#else
Type[] genArgs;
#if WINRT || COREFX
if (typeInfo.IsGenericType && type.GetGenericTypeDefinition() == typeof(System.Collections.Generic.IDictionary<,>)
&& itemType == typeof(System.Collections.Generic.KeyValuePair<,>).MakeGenericType(genArgs = typeInfo.GenericTypeArguments))
#else
if (type.IsGenericType && type.GetGenericTypeDefinition() == model.MapType(typeof(System.Collections.Generic.IDictionary<,>))
&& itemType == model.MapType(typeof(System.Collections.Generic.KeyValuePair<,>)).MakeGenericType(genArgs = type.GetGenericArguments()))
#endif
{
defaultType = model.MapType(typeof(System.Collections.Generic.Dictionary<,>)).MakeGenericType(genArgs);
}
else
{
defaultType = model.MapType(typeof(System.Collections.Generic.List<>)).MakeGenericType(itemType);
}
#endif
}
}
// verify that the default type is appropriate
if (defaultType != null && !Helpers.IsAssignableFrom(type, defaultType)) { defaultType = null; }
}
}
private void Add(ValueMember member) {
int opaqueToken = 0;
try {
model.TakeLock(ref opaqueToken);
ThrowIfFrozen();
fields.Add(member);
} finally
{
model.ReleaseLock(opaqueToken);
}
}
///
/// Returns the ValueMember that matchs a given field number, or null if not found
///
public ValueMember this[int fieldNumber]
{
get
{
foreach (ValueMember member in fields)
{
if (member.FieldNumber == fieldNumber) return member;
}
return null;
}
}
///
/// Returns the ValueMember that matchs a given member (property/field), or null if not found
///
public ValueMember this[MemberInfo member]
{
get
{
if (member == null) return null;
foreach (ValueMember x in fields)
{
if (x.Member == member) return x;
}
return null;
}
}
private readonly BasicList fields = new BasicList();
///
/// Returns the ValueMember instances associated with this type
///
public ValueMember[] GetFields() {
ValueMember[] arr = new ValueMember[fields.Count];
fields.CopyTo(arr, 0);
Array.Sort(arr, ValueMember.Comparer.Default);
return arr;
}
///
/// Returns the SubType instances associated with this type
///
public SubType[] GetSubtypes()
{
if (subTypes == null || subTypes.Count == 0) return new SubType[0];
SubType[] arr = new SubType[subTypes.Count];
subTypes.CopyTo(arr, 0);
Array.Sort(arr, SubType.Comparer.Default);
return arr;
}
#if FEAT_COMPILER && !FX11
///
/// Compiles the serializer for this type; this is *not* a full
/// standalone compile, but can significantly boost performance
/// while allowing additional types to be added.
///
/// An in-place compile can access non-public types / members
public void CompileInPlace()
{
#if FEAT_IKVM
// just no nothing, quietely; don't want to break the API
#else
serializer = CompiledSerializer.Wrap(Serializer, model);
#endif
}
#endif
internal bool IsDefined(int fieldNumber)
{
foreach (ValueMember field in fields)
{
if (field.FieldNumber == fieldNumber) return true;
}
return false;
}
internal int GetKey(bool demand, bool getBaseKey)
{
return model.GetKey(type, demand, getBaseKey);
}
internal EnumSerializer.EnumPair[] GetEnumMap()
{
if (HasFlag(OPTIONS_EnumPassThru)) return null;
EnumSerializer.EnumPair[] result = new EnumSerializer.EnumPair[fields.Count];
for (int i = 0; i < result.Length; i++)
{
ValueMember member = (ValueMember) fields[i];
int wireValue = member.FieldNumber;
object value = member.GetRawEnumValue();
result[i] = new EnumSerializer.EnumPair(wireValue, value, member.MemberType);
}
return result;
}
///
/// Gets or sets a value indicating that an enum should be treated directly as an int/short/etc, rather
/// than enforcing .proto enum rules. This is useful *in particul* for [Flags] enums.
///
public bool EnumPassthru
{
get { return HasFlag(OPTIONS_EnumPassThru); }
set { SetFlag(OPTIONS_EnumPassThru, value, true); }
}
///
/// Gets or sets a value indicating that this type should NOT be treated as a list, even if it has
/// familiar list-like characteristics (enumerable, add, etc)
///
public bool IgnoreListHandling
{
get { return HasFlag(OPTIONS_IgnoreListHandling); }
set { SetFlag(OPTIONS_IgnoreListHandling, value, true); }
}
internal bool Pending
{
get { return HasFlag(OPTIONS_Pending); }
set { SetFlag(OPTIONS_Pending, value, false); }
}
private const byte
OPTIONS_Pending = 1,
OPTIONS_EnumPassThru = 2,
OPTIONS_Frozen = 4,
OPTIONS_PrivateOnApi = 8,
OPTIONS_SkipConstructor = 16,
OPTIONS_AsReferenceDefault = 32,
OPTIONS_AutoTuple = 64,
OPTIONS_IgnoreListHandling = 128;
private volatile byte flags;
private bool HasFlag(byte flag) { return (flags & flag) == flag; }
private void SetFlag(byte flag, bool value, bool throwIfFrozen)
{
if (throwIfFrozen && HasFlag(flag) != value)
{
ThrowIfFrozen();
}
if (value)
flags |= flag;
else
flags = (byte)(flags & ~flag);
}
internal static MetaType GetRootType(MetaType source)
{
while (source.serializer != null)
{
MetaType tmp = source.baseType;
if (tmp == null) return source;
source = tmp; // else loop until we reach something that isn't generated, or is the root
}
// now we get into uncertain territory
RuntimeTypeModel model = source.model;
int opaqueToken = 0;
try {
model.TakeLock(ref opaqueToken);
MetaType tmp;
while ((tmp = source.baseType) != null) source = tmp;
return source;
} finally {
model.ReleaseLock(opaqueToken);
}
}
internal bool IsPrepared()
{
#if FEAT_COMPILER && !FEAT_IKVM && !FX11
return serializer is CompiledSerializer;
#else
return false;
#endif
}
internal System.Collections.IEnumerable Fields { get { return this.fields; } }
internal static System.Text.StringBuilder NewLine(System.Text.StringBuilder builder, int indent)
{
return Helpers.AppendLine(builder).Append(' ', indent*3);
}
internal bool IsAutoTuple
{
get { return HasFlag(OPTIONS_AutoTuple); }
}
internal void WriteSchema(System.Text.StringBuilder builder, int indent, ref bool requiresBclImport)
{
if (surrogate != null) return; // nothing to write
ValueMember[] fieldsArr = new ValueMember[fields.Count];
fields.CopyTo(fieldsArr, 0);
Array.Sort(fieldsArr, ValueMember.Comparer.Default);
if (IsList)
{
string itemTypeName = model.GetSchemaTypeName(TypeModel.GetListItemType(model, type), DataFormat.Default, false, false, ref requiresBclImport);
NewLine(builder, indent).Append("message ").Append(GetSchemaTypeName()).Append(" {");
NewLine(builder, indent + 1).Append("repeated ").Append(itemTypeName).Append(" items = 1;");
NewLine(builder, indent).Append('}');
}
else if (IsAutoTuple)
{ // key-value-pair etc
MemberInfo[] mapping;
if(ResolveTupleConstructor(type, out mapping) != null)
{
NewLine(builder, indent).Append("message ").Append(GetSchemaTypeName()).Append(" {");
for(int i = 0 ; i < mapping.Length ; i++)
{
Type effectiveType;
if(mapping[i] is PropertyInfo)
{
effectiveType = ((PropertyInfo) mapping[i]).PropertyType;
} else if (mapping[i] is FieldInfo)
{
effectiveType = ((FieldInfo) mapping[i]).FieldType;
} else
{
throw new NotSupportedException("Unknown member type: " + mapping[i].GetType().Name);
}
NewLine(builder, indent + 1).Append("optional ").Append(model.GetSchemaTypeName(effectiveType, DataFormat.Default, false, false, ref requiresBclImport).Replace('.','_'))
.Append(' ').Append(mapping[i].Name).Append(" = ").Append(i + 1).Append(';');
}
NewLine(builder, indent).Append('}');
}
}
else if(Helpers.IsEnum(type))
{
NewLine(builder, indent).Append("enum ").Append(GetSchemaTypeName()).Append(" {");
if (fieldsArr.Length == 0 && EnumPassthru) {
if (type
#if WINRT || COREFX
.GetTypeInfo()
#endif
.IsDefined(model.MapType(typeof(FlagsAttribute)), false))
{
NewLine(builder, indent + 1).Append("// this is a composite/flags enumeration");
}
else
{
NewLine(builder, indent + 1).Append("// this enumeration will be passed as a raw value");
}
foreach(FieldInfo field in
#if WINRT
type.GetRuntimeFields()
#else
type.GetFields()
#endif
)
{
if(field.IsStatic && field.IsLiteral)
{
object enumVal;
#if WINRT || PORTABLE || CF || FX11 || NETSTANDARD1_3 || NETSTANDARD1_4
enumVal = Convert.ChangeType(field.GetValue(null), Enum.GetUnderlyingType(field.FieldType));
#else
enumVal = field.GetRawConstantValue();
#endif
NewLine(builder, indent + 1).Append(field.Name).Append(" = ").Append(enumVal).Append(";");
}
}
}
else
{
foreach (ValueMember member in fieldsArr)
{
NewLine(builder, indent + 1).Append(member.Name).Append(" = ").Append(member.FieldNumber).Append(';');
}
}
NewLine(builder, indent).Append('}');
} else
{
NewLine(builder, indent).Append("message ").Append(GetSchemaTypeName()).Append(" {");
foreach (ValueMember member in fieldsArr)
{
string ordinality = member.ItemType != null ? "repeated" : member.IsRequired ? "required" : "optional";
NewLine(builder, indent + 1).Append(ordinality).Append(' ');
if (member.DataFormat == DataFormat.Group) builder.Append("group ");
string schemaTypeName = member.GetSchemaTypeName(true, ref requiresBclImport);
builder.Append(schemaTypeName).Append(" ")
.Append(member.Name).Append(" = ").Append(member.FieldNumber);
if(member.DefaultValue != null && member.IsRequired == false)
{
if (member.DefaultValue is string)
{
builder.Append(" [default = \"").Append(member.DefaultValue).Append("\"]");
}
else if(member.DefaultValue is bool)
{ // need to be lower case (issue 304)
builder.Append((bool)member.DefaultValue ? " [default = true]" : " [default = false]");
}
else
{
builder.Append(" [default = ").Append(member.DefaultValue).Append(']');
}
}
if(member.ItemType != null && member.IsPacked)
{
builder.Append(" [packed=true]");
}
builder.Append(';');
if (schemaTypeName == "bcl.NetObjectProxy" && member.AsReference && !member.DynamicType) // we know what it is; tell the user
{
builder.Append(" // reference-tracked ").Append(member.GetSchemaTypeName(false, ref requiresBclImport));
}
}
if (subTypes != null && subTypes.Count != 0)
{
NewLine(builder, indent + 1).Append("// the following represent sub-types; at most 1 should have a value");
SubType[] subTypeArr = new SubType[subTypes.Count];
subTypes.CopyTo(subTypeArr, 0);
Array.Sort(subTypeArr, SubType.Comparer.Default);
foreach (SubType subType in subTypeArr)
{
string subTypeName = subType.DerivedType.GetSchemaTypeName();
NewLine(builder, indent + 1).Append("optional ").Append(subTypeName)
.Append(" ").Append(subTypeName).Append(" = ").Append(subType.FieldNumber).Append(';');
}
}
NewLine(builder, indent).Append('}');
}
}
}
}
#endif