#if !NO_RUNTIME
using System;
using System.Collections;
using System.Text;
#if FEAT_IKVM
using Type = IKVM.Reflection.Type;
using IKVM.Reflection;
using IKVM.Reflection.Emit;
#else
using System.Reflection;
#if FEAT_COMPILER
using System.Reflection.Emit;
#endif
#endif
using ProtoBuf.Serializers;
using System.Threading;
using System.IO;
namespace ProtoBuf.Meta
{
///
/// Provides protobuf serialization support for a number of types that can be defined at runtime
///
public sealed class RuntimeTypeModel : TypeModel
{
private ushort options;
private const ushort
OPTIONS_InferTagFromNameDefault = 1,
OPTIONS_IsDefaultModel = 2,
OPTIONS_Frozen = 4,
OPTIONS_AutoAddMissingTypes = 8,
#if FEAT_COMPILER && !FX11
OPTIONS_AutoCompile = 16,
#endif
OPTIONS_UseImplicitZeroDefaults = 32,
OPTIONS_AllowParseableTypes = 64,
OPTIONS_AutoAddProtoContractTypesOnly = 128,
OPTIONS_IncludeDateTimeKind = 256;
private bool GetOption(ushort option)
{
return (options & option) == option;
}
private void SetOption(ushort option, bool value)
{
if (value) options |= option;
else options &= (ushort)~option;
}
///
/// Global default that
/// enables/disables automatic tag generation based on the existing name / order
/// of the defined members. See
/// for usage and important warning / explanation.
/// You must set the global default before attempting to serialize/deserialize any
/// impacted type.
///
public bool InferTagFromNameDefault
{
get { return GetOption(OPTIONS_InferTagFromNameDefault); }
set { SetOption(OPTIONS_InferTagFromNameDefault, value); }
}
///
/// Global default that determines whether types are considered serializable
/// if they have [DataContract] / [XmlType]. With this enabled, ONLY
/// types marked as [ProtoContract] are added automatically.
///
public bool AutoAddProtoContractTypesOnly
{
get { return GetOption(OPTIONS_AutoAddProtoContractTypesOnly); }
set { SetOption(OPTIONS_AutoAddProtoContractTypesOnly, value); }
}
///
/// Global switch that enables or disables the implicit
/// handling of "zero defaults"; meanning: if no other default is specified,
/// it assumes bools always default to false, integers to zero, etc.
///
/// If this is disabled, no such assumptions are made and only *explicit*
/// default values are processed. This is enabled by default to
/// preserve similar logic to v1.
///
public bool UseImplicitZeroDefaults
{
get {return GetOption(OPTIONS_UseImplicitZeroDefaults);}
set {
if (!value && GetOption(OPTIONS_IsDefaultModel))
{
throw new InvalidOperationException("UseImplicitZeroDefaults cannot be disabled on the default model");
}
SetOption(OPTIONS_UseImplicitZeroDefaults, value);
}
}
///
/// Global switch that determines whether types with a .ToString() and a Parse(string)
/// should be serialized as strings.
///
public bool AllowParseableTypes
{
get { return GetOption(OPTIONS_AllowParseableTypes); }
set { SetOption(OPTIONS_AllowParseableTypes, value); }
}
///
/// Global switch that determines whether DateTime serialization should include the Kind of the date/time.
///
public bool IncludeDateTimeKind
{
get { return GetOption(OPTIONS_IncludeDateTimeKind); }
set { SetOption(OPTIONS_IncludeDateTimeKind, value); }
}
///
/// Should the Kind be included on date/time values?
///
protected internal override bool SerializeDateTimeKind()
{
return GetOption(OPTIONS_IncludeDateTimeKind);
}
private sealed class Singleton
{
private Singleton() { }
internal static readonly RuntimeTypeModel Value = new RuntimeTypeModel(true);
}
///
/// The default model, used to support ProtoBuf.Serializer
///
public static RuntimeTypeModel Default
{
get { return Singleton.Value; }
}
///
/// Returns a sequence of the Type instances that can be
/// processed by this model.
///
public IEnumerable GetTypes() { return types; }
///
/// Suggest a .proto definition for the given type
///
/// The type to generate a .proto definition for, or null to generate a .proto that represents the entire model
/// The .proto definition as a string
public override string GetSchema(Type type)
{
BasicList requiredTypes = new BasicList();
MetaType primaryType = null;
bool isInbuiltType = false;
if (type == null)
{ // generate for the entire model
foreach(MetaType meta in types)
{
MetaType tmp = meta.GetSurrogateOrBaseOrSelf(false);
if (!requiredTypes.Contains(tmp))
{ // ^^^ note that the type might have been added as a descendent
requiredTypes.Add(tmp);
CascadeDependents(requiredTypes, tmp);
}
}
}
else
{
Type tmp = Helpers.GetUnderlyingType(type);
if (tmp != null) type = tmp;
WireType defaultWireType;
isInbuiltType = (ValueMember.TryGetCoreSerializer(this, DataFormat.Default, type, out defaultWireType, false, false, false, false) != null);
if (!isInbuiltType)
{
//Agenerate just relative to the supplied type
int index = FindOrAddAuto(type, false, false, false);
if (index < 0) throw new ArgumentException("The type specified is not a contract-type", "type");
// get the required types
primaryType = ((MetaType)types[index]).GetSurrogateOrBaseOrSelf(false);
requiredTypes.Add(primaryType);
CascadeDependents(requiredTypes, primaryType);
}
}
// use the provided type's namespace for the "package"
StringBuilder headerBuilder = new StringBuilder();
string package = null;
if (!isInbuiltType)
{
IEnumerable typesForNamespace = primaryType == null ? types : requiredTypes;
foreach (MetaType meta in typesForNamespace)
{
if (meta.IsList) continue;
string tmp = meta.Type.Namespace;
if (!Helpers.IsNullOrEmpty(tmp))
{
if (tmp.StartsWith("System.")) continue;
if (package == null)
{ // haven't seen any suggestions yet
package = tmp;
}
else if (package == tmp)
{ // that's fine; a repeat of the one we already saw
}
else
{ // something else; have confliucting suggestions; abort
package = null;
break;
}
}
}
}
if (!Helpers.IsNullOrEmpty(package))
{
headerBuilder.Append("package ").Append(package).Append(';');
Helpers.AppendLine(headerBuilder);
}
bool requiresBclImport = false;
StringBuilder bodyBuilder = new StringBuilder();
// sort them by schema-name
MetaType[] metaTypesArr = new MetaType[requiredTypes.Count];
requiredTypes.CopyTo(metaTypesArr, 0);
Array.Sort(metaTypesArr, MetaType.Comparer.Default);
// write the messages
if (isInbuiltType)
{
Helpers.AppendLine(bodyBuilder).Append("message ").Append(type.Name).Append(" {");
MetaType.NewLine(bodyBuilder, 1).Append("optional ").Append(GetSchemaTypeName(type, DataFormat.Default, false, false, ref requiresBclImport))
.Append(" value = 1;");
Helpers.AppendLine(bodyBuilder).Append('}');
}
else
{
for (int i = 0; i < metaTypesArr.Length; i++)
{
MetaType tmp = metaTypesArr[i];
if (tmp.IsList && tmp != primaryType) continue;
tmp.WriteSchema(bodyBuilder, 0, ref requiresBclImport);
}
}
if (requiresBclImport)
{
headerBuilder.Append("import \"bcl.proto\"; // schema for protobuf-net's handling of core .NET types");
Helpers.AppendLine(headerBuilder);
}
return Helpers.AppendLine(headerBuilder.Append(bodyBuilder)).ToString();
}
private void CascadeDependents(BasicList list, MetaType metaType)
{
MetaType tmp;
if (metaType.IsList)
{
Type itemType = TypeModel.GetListItemType(this, metaType.Type);
WireType defaultWireType;
IProtoSerializer coreSerializer = ValueMember.TryGetCoreSerializer(this, DataFormat.Default, itemType, out defaultWireType, false, false, false, false);
if (coreSerializer == null)
{
int index = FindOrAddAuto(itemType, false, false, false);
if (index >= 0)
{
tmp = ((MetaType)types[index]).GetSurrogateOrBaseOrSelf(false);
if (!list.Contains(tmp))
{ // could perhaps also implement as a queue, but this should work OK for sane models
list.Add(tmp);
CascadeDependents(list, tmp);
}
}
}
}
else
{
if (metaType.IsAutoTuple)
{
MemberInfo[] mapping;
if(MetaType.ResolveTupleConstructor(metaType.Type, out mapping) != null)
{
for (int i = 0; i < mapping.Length; i++)
{
Type type = null;
if (mapping[i] is PropertyInfo) type = ((PropertyInfo)mapping[i]).PropertyType;
else if (mapping[i] is FieldInfo) type = ((FieldInfo)mapping[i]).FieldType;
WireType defaultWireType;
IProtoSerializer coreSerializer = ValueMember.TryGetCoreSerializer(this, DataFormat.Default, type, out defaultWireType, false, false, false, false);
if (coreSerializer == null)
{
int index = FindOrAddAuto(type, false, false, false);
if (index >= 0)
{
tmp = ((MetaType)types[index]).GetSurrogateOrBaseOrSelf(false);
if (!list.Contains(tmp))
{ // could perhaps also implement as a queue, but this should work OK for sane models
list.Add(tmp);
CascadeDependents(list, tmp);
}
}
}
}
}
}
else
{
foreach (ValueMember member in metaType.Fields)
{
Type type = member.ItemType;
if (type == null) type = member.MemberType;
WireType defaultWireType;
IProtoSerializer coreSerializer = ValueMember.TryGetCoreSerializer(this, DataFormat.Default, type, out defaultWireType, false, false, false, false);
if (coreSerializer == null)
{
// is an interesting type
int index = FindOrAddAuto(type, false, false, false);
if (index >= 0)
{
tmp = ((MetaType)types[index]).GetSurrogateOrBaseOrSelf(false);
if (!list.Contains(tmp))
{ // could perhaps also implement as a queue, but this should work OK for sane models
list.Add(tmp);
CascadeDependents(list, tmp);
}
}
}
}
}
if (metaType.HasSubtypes)
{
foreach (SubType subType in metaType.GetSubtypes())
{
tmp = subType.DerivedType.GetSurrogateOrSelf(); // note: exclude base-types!
if (!list.Contains(tmp))
{
list.Add(tmp);
CascadeDependents(list, tmp);
}
}
}
tmp = metaType.BaseType;
if (tmp != null) tmp = tmp.GetSurrogateOrSelf(); // note: already walking base-types; exclude base
if (tmp != null && !list.Contains(tmp))
{
list.Add(tmp);
CascadeDependents(list, tmp);
}
}
}
internal RuntimeTypeModel(bool isDefault)
{
#if FEAT_IKVM
universe = new IKVM.Reflection.Universe();
universe.EnableMissingMemberResolution(); // needed to avoid TypedReference issue on WinRT
#endif
AutoAddMissingTypes = true;
UseImplicitZeroDefaults = true;
SetOption(OPTIONS_IsDefaultModel, isDefault);
#if FEAT_COMPILER && !FX11 && !DEBUG
AutoCompile = true;
#endif
}
#if FEAT_IKVM
readonly IKVM.Reflection.Universe universe;
///
/// Load an assembly into the model's universe
///
public Assembly Load(string path)
{
return universe.LoadFile(path);
}
///
/// Gets the IKVM Universe that relates to this model
///
public Universe Universe { get { return universe; } }
///
/// Adds support for an additional type in this model, optionally
/// applying inbuilt patterns. If the type is already known to the
/// model, the existing type is returned **without** applying
/// any additional behaviour.
///
public MetaType Add(string assemblyQualifiedTypeName, bool applyDefaults)
{
Type type = universe.GetType(assemblyQualifiedTypeName, true);
return Add(type, applyDefaults);
}
///
/// Adds support for an additional type in this model, optionally
/// applying inbuilt patterns. If the type is already known to the
/// model, the existing type is returned **without** applying
/// any additional behaviour.
///
public MetaType Add(System.Type type, bool applyDefaultBehaviour)
{
return Add(MapType(type), applyDefaultBehaviour);
}
///
/// Obtains the MetaType associated with a given Type for the current model,
/// allowing additional configuration.
///
public MetaType this[System.Type type] { get { return this[MapType(type)]; } }
#endif
///
/// Obtains the MetaType associated with a given Type for the current model,
/// allowing additional configuration.
///
public MetaType this[Type type] { get { return (MetaType)types[FindOrAddAuto(type, true, false, false)]; } }
internal MetaType FindWithoutAdd(Type type)
{
// this list is thread-safe for reading
foreach (MetaType metaType in types)
{
if (metaType.Type == type)
{
if (metaType.Pending) WaitOnLock(metaType);
return metaType;
}
}
// if that failed, check for a proxy
Type underlyingType = ResolveProxies(type);
return underlyingType == null ? null : FindWithoutAdd(underlyingType);
}
static readonly BasicList.MatchPredicate
MetaTypeFinder = new BasicList.MatchPredicate(MetaTypeFinderImpl),
BasicTypeFinder = new BasicList.MatchPredicate(BasicTypeFinderImpl);
static bool MetaTypeFinderImpl(object value, object ctx)
{
return ((MetaType)value).Type == (Type)ctx;
}
static bool BasicTypeFinderImpl(object value, object ctx)
{
return ((BasicType)value).Type == (Type)ctx;
}
private void WaitOnLock(MetaType type)
{
int opaqueToken = 0;
try
{
TakeLock(ref opaqueToken);
}
finally
{
ReleaseLock(opaqueToken);
}
}
BasicList basicTypes = new BasicList();
sealed class BasicType
{
private readonly Type type;
public Type Type { get { return type; } }
private readonly IProtoSerializer serializer;
public IProtoSerializer Serializer { get { return serializer; } }
public BasicType(Type type, IProtoSerializer serializer)
{
this.type = type;
this.serializer = serializer;
}
}
internal IProtoSerializer TryGetBasicTypeSerializer(Type type)
{
int idx = basicTypes.IndexOf(BasicTypeFinder, type);
if (idx >= 0) return ((BasicType)basicTypes[idx]).Serializer;
lock(basicTypes)
{ // don't need a full model lock for this
// double-checked
idx = basicTypes.IndexOf(BasicTypeFinder, type);
if (idx >= 0) return ((BasicType)basicTypes[idx]).Serializer;
WireType defaultWireType;
MetaType.AttributeFamily family = MetaType.GetContractFamily(this, type, null);
IProtoSerializer ser = family == MetaType.AttributeFamily.None
? ValueMember.TryGetCoreSerializer(this, DataFormat.Default, type, out defaultWireType, false, false, false, false)
: null;
if(ser != null) basicTypes.Add(new BasicType(type, ser));
return ser;
}
}
internal int FindOrAddAuto(Type type, bool demand, bool addWithContractOnly, bool addEvenIfAutoDisabled)
{
int key = types.IndexOf(MetaTypeFinder, type);
MetaType metaType;
// the fast happy path: meta-types we've already seen
if (key >= 0)
{
metaType = (MetaType)types[key];
if (metaType.Pending)
{
WaitOnLock(metaType);
}
return key;
}
// the fast fail path: types that will never have a meta-type
bool shouldAdd = AutoAddMissingTypes || addEvenIfAutoDisabled;
if (!Helpers.IsEnum(type) && TryGetBasicTypeSerializer(type) != null)
{
if (shouldAdd && !addWithContractOnly) throw MetaType.InbuiltType(type);
return -1; // this will never be a meta-type
}
// otherwise: we don't yet know
// check for proxy types
Type underlyingType = ResolveProxies(type);
if (underlyingType != null)
{
key = types.IndexOf(MetaTypeFinder, underlyingType);
type = underlyingType; // if new added, make it reflect the underlying type
}
if (key < 0)
{
int opaqueToken = 0;
try
{
TakeLock(ref opaqueToken);
// try to recognise a few familiar patterns...
if ((metaType = RecogniseCommonTypes(type)) == null)
{ // otherwise, check if it is a contract
MetaType.AttributeFamily family = MetaType.GetContractFamily(this, type, null);
if (family == MetaType.AttributeFamily.AutoTuple)
{
shouldAdd = addEvenIfAutoDisabled = true; // always add basic tuples, such as KeyValuePair
}
if (!shouldAdd || (
!Helpers.IsEnum(type) && addWithContractOnly && family == MetaType.AttributeFamily.None)
)
{
if (demand) ThrowUnexpectedType(type);
return key;
}
metaType = Create(type);
}
metaType.Pending = true;
bool weAdded = false;
// double-checked
int winner = types.IndexOf(MetaTypeFinder, type);
if (winner < 0)
{
ThrowIfFrozen();
key = types.Add(metaType);
weAdded = true;
}
else
{
key = winner;
}
if (weAdded)
{
metaType.ApplyDefaultBehaviour();
metaType.Pending = false;
}
}
finally
{
ReleaseLock(opaqueToken);
}
}
return key;
}
private MetaType RecogniseCommonTypes(Type type)
{
//#if !NO_GENERICS
// if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(System.Collections.Generic.KeyValuePair<,>))
// {
// MetaType mt = new MetaType(this, type);
// Type surrogate = typeof (KeyValuePairSurrogate<,>).MakeGenericType(type.GetGenericArguments());
// mt.SetSurrogate(surrogate);
// mt.IncludeSerializerMethod = false;
// mt.Freeze();
// MetaType surrogateMeta = (MetaType)types[FindOrAddAuto(surrogate, true, true, true)]; // this forcibly adds it if needed
// if(surrogateMeta.IncludeSerializerMethod)
// { // don't blindly set - it might be frozen
// surrogateMeta.IncludeSerializerMethod = false;
// }
// surrogateMeta.Freeze();
// return mt;
// }
//#endif
return null;
}
private MetaType Create(Type type)
{
ThrowIfFrozen();
return new MetaType(this, type, defaultFactory);
}
///
/// Adds support for an additional type in this model, optionally
/// applying inbuilt patterns. If the type is already known to the
/// model, the existing type is returned **without** applying
/// any additional behaviour.
///
/// Inbuilt patterns include:
/// [ProtoContract]/[ProtoMember(n)]
/// [DataContract]/[DataMember(Order=n)]
/// [XmlType]/[XmlElement(Order=n)]
/// [On{Des|S}erializ{ing|ed}]
/// ShouldSerialize*/*Specified
///
/// The type to be supported
/// Whether to apply the inbuilt configuration patterns (via attributes etc), or
/// just add the type with no additional configuration (the type must then be manually configured).
/// The MetaType representing this type, allowing
/// further configuration.
public MetaType Add(Type type, bool applyDefaultBehaviour)
{
if (type == null) throw new ArgumentNullException("type");
MetaType newType = FindWithoutAdd(type);
if (newType != null) return newType; // return existing
int opaqueToken = 0;
#if WINRT || COREFX
System.Reflection.TypeInfo typeInfo = System.Reflection.IntrospectionExtensions.GetTypeInfo(type);
if (typeInfo.IsInterface && MetaType.ienumerable.IsAssignableFrom(typeInfo)
#else
if (type.IsInterface && MapType(MetaType.ienumerable).IsAssignableFrom(type)
#endif
&& GetListItemType(this, type) == null)
{
throw new ArgumentException("IEnumerable[] data cannot be used as a meta-type unless an Add method can be resolved");
}
try
{
newType = RecogniseCommonTypes(type);
if(newType != null)
{
if(!applyDefaultBehaviour) {
throw new ArgumentException(
"Default behaviour must be observed for certain types with special handling; " + type.FullName,
"applyDefaultBehaviour");
}
// we should assume that type is fully configured, though; no need to re-run:
applyDefaultBehaviour = false;
}
if(newType == null) newType = Create(type);
newType.Pending = true;
TakeLock(ref opaqueToken);
// double checked
if (FindWithoutAdd(type) != null) throw new ArgumentException("Duplicate type", "type");
ThrowIfFrozen();
types.Add(newType);
if (applyDefaultBehaviour) { newType.ApplyDefaultBehaviour(); }
newType.Pending = false;
}
finally
{
ReleaseLock(opaqueToken);
}
return newType;
}
#if FEAT_COMPILER && !FX11
///
/// Should serializers be compiled on demand? It may be useful
/// to disable this for debugging purposes.
///
public bool AutoCompile
{
get { return GetOption(OPTIONS_AutoCompile); }
set { SetOption(OPTIONS_AutoCompile, value); }
}
#endif
///
/// Should support for unexpected types be added automatically?
/// If false, an exception is thrown when unexpected types
/// are encountered.
///
public bool AutoAddMissingTypes
{
get { return GetOption(OPTIONS_AutoAddMissingTypes); }
set {
if (!value && GetOption(OPTIONS_IsDefaultModel))
{
throw new InvalidOperationException("The default model must allow missing types");
}
ThrowIfFrozen();
SetOption(OPTIONS_AutoAddMissingTypes, value);
}
}
///
/// Verifies that the model is still open to changes; if not, an exception is thrown
///
private void ThrowIfFrozen()
{
if (GetOption(OPTIONS_Frozen)) throw new InvalidOperationException("The model cannot be changed once frozen");
}
///
/// Prevents further changes to this model
///
public void Freeze()
{
if (GetOption(OPTIONS_IsDefaultModel)) throw new InvalidOperationException("The default model cannot be frozen");
SetOption(OPTIONS_Frozen, true);
}
private readonly BasicList types = new BasicList();
///
/// Provides the key that represents a given type in the current model.
///
protected override int GetKeyImpl(Type type)
{
return GetKey(type, false, true);
}
internal int GetKey(Type type, bool demand, bool getBaseKey)
{
Helpers.DebugAssert(type != null);
try
{
int typeIndex = FindOrAddAuto(type, demand, true, false);
if (typeIndex >= 0)
{
MetaType mt = (MetaType)types[typeIndex];
if (getBaseKey)
{
mt = MetaType.GetRootType(mt);
typeIndex = FindOrAddAuto(mt.Type, true, true, false);
}
}
return typeIndex;
}
catch (NotSupportedException)
{
throw; // re-surface "as-is"
}
catch (Exception ex)
{
if (ex.Message.IndexOf(type.FullName) >= 0) throw; // already enough info
throw new ProtoException(ex.Message + " (" + type.FullName + ")", ex);
}
}
///
/// Writes a protocol-buffer representation of the given instance to the supplied stream.
///
/// Represents the type (including inheritance) to consider.
/// The existing instance to be serialized (cannot be null).
/// The destination stream to write to.
protected internal override void Serialize(int key, object value, ProtoWriter dest)
{
#if FEAT_IKVM
throw new NotSupportedException();
#else
//Helpers.DebugWriteLine("Serialize", value);
((MetaType)types[key]).Serializer.Write(value, dest);
#endif
}
///
/// Applies a protocol-buffer stream to an existing instance (which may be null).
///
/// Represents the type (including inheritance) to consider.
/// The existing instance to be modified (can be null).
/// The binary stream to apply to the instance (cannot be null).
/// The updated instance; this may be different to the instance argument if
/// either the original instance was null, or the stream defines a known sub-type of the
/// original instance.
protected internal override object Deserialize(int key, object value, ProtoReader source)
{
#if FEAT_IKVM
throw new NotSupportedException();
#else
//Helpers.DebugWriteLine("Deserialize", value);
IProtoSerializer ser = ((MetaType)types[key]).Serializer;
if (value == null && Helpers.IsValueType(ser.ExpectedType)) {
if(ser.RequiresOldValue) value = Activator.CreateInstance(ser.ExpectedType);
return ser.Read(value, source);
} else {
return ser.Read(value, source);
}
#endif
}
#if FEAT_COMPILER
// this is used by some unit-tests; do not remove
internal Compiler.ProtoSerializer GetSerializer(IProtoSerializer serializer, bool compiled)
{
#if FEAT_IKVM
throw new NotSupportedException();
#else
if (serializer == null) throw new ArgumentNullException("serializer");
#if FEAT_COMPILER && !FX11
if (compiled) return Compiler.CompilerContext.BuildSerializer(serializer, this);
#endif
return new Compiler.ProtoSerializer(serializer.Write);
#endif
}
#if !FX11
///
/// Compiles the serializers individually; 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()
{
foreach (MetaType type in types)
{
type.CompileInPlace();
}
}
#endif
#endif
//internal override IProtoSerializer GetTypeSerializer(Type type)
//{ // this list is thread-safe for reading
// .Serializer;
//}
//internal override IProtoSerializer GetTypeSerializer(int key)
//{ // this list is thread-safe for reading
// MetaType type = (MetaType)types.TryGet(key);
// if (type != null) return type.Serializer;
// throw new KeyNotFoundException();
//}
#if FEAT_COMPILER
private void BuildAllSerializers()
{
// note that types.Count may increase during this operation, as some serializers
// bring other types into play
for (int i = 0; i < types.Count; i++)
{
// the primary purpose of this is to force the creation of the Serializer
MetaType mt = (MetaType)types[i];
if (mt.Serializer == null)
throw new InvalidOperationException("No serializer available for " + mt.Type.Name);
}
}
#if !SILVERLIGHT
internal sealed class SerializerPair : IComparable
{
int IComparable.CompareTo(object obj)
{
if (obj == null) throw new ArgumentException("obj");
SerializerPair other = (SerializerPair)obj;
// we want to bunch all the items with the same base-type together, but we need the items with a
// different base **first**.
if (this.BaseKey == this.MetaKey)
{
if (other.BaseKey == other.MetaKey)
{ // neither is a subclass
return this.MetaKey.CompareTo(other.MetaKey);
}
else
{ // "other" (only) is involved in inheritance; "other" should be first
return 1;
}
}
else
{
if (other.BaseKey == other.MetaKey)
{ // "this" (only) is involved in inheritance; "this" should be first
return -1;
}
else
{ // both are involved in inheritance
int result = this.BaseKey.CompareTo(other.BaseKey);
if (result == 0) result = this.MetaKey.CompareTo(other.MetaKey);
return result;
}
}
}
public readonly int MetaKey, BaseKey;
public readonly MetaType Type;
public readonly MethodBuilder Serialize, Deserialize;
public readonly ILGenerator SerializeBody, DeserializeBody;
public SerializerPair(int metaKey, int baseKey, MetaType type, MethodBuilder serialize, MethodBuilder deserialize,
ILGenerator serializeBody, ILGenerator deserializeBody)
{
this.MetaKey = metaKey;
this.BaseKey = baseKey;
this.Serialize = serialize;
this.Deserialize = deserialize;
this.SerializeBody = serializeBody;
this.DeserializeBody = deserializeBody;
this.Type = type;
}
}
///
/// Fully compiles the current model into a static-compiled model instance
///
/// A full compilation is restricted to accessing public types / members
/// An instance of the newly created compiled type-model
public TypeModel Compile()
{
CompilerOptions options = new CompilerOptions();
return Compile(options);
}
static ILGenerator Override(TypeBuilder type, string name)
{
MethodInfo baseMethod = type.BaseType.GetMethod(name, BindingFlags.NonPublic | BindingFlags.Instance);
ParameterInfo[] parameters = baseMethod.GetParameters();
Type[] paramTypes = new Type[parameters.Length];
for(int i = 0 ; i < paramTypes.Length ; i++) {
paramTypes[i] = parameters[i].ParameterType;
}
MethodBuilder newMethod = type.DefineMethod(baseMethod.Name,
(baseMethod.Attributes & ~MethodAttributes.Abstract) | MethodAttributes.Final, baseMethod.CallingConvention, baseMethod.ReturnType, paramTypes);
ILGenerator il = newMethod.GetILGenerator();
type.DefineMethodOverride(newMethod, baseMethod);
return il;
}
#if FEAT_IKVM
///
/// Inspect the model, and resolve all related types
///
public void Cascade()
{
BuildAllSerializers();
}
///
/// Translate a System.Type into the universe's type representation
///
protected internal override Type MapType(System.Type type, bool demand)
{
if (type == null) return null;
#if DEBUG
if (type.Assembly == typeof(IKVM.Reflection.Type).Assembly)
{
throw new InvalidOperationException(string.Format(
"Somebody is passing me IKVM types! {0} should be fully-qualified at the call-site",
type.Name));
}
#endif
Type result = universe.GetType(type.AssemblyQualifiedName);
if(result == null)
{
// things also tend to move around... *a lot* - especially in WinRT; search all as a fallback strategy
foreach (Assembly a in universe.GetAssemblies())
{
result = a.GetType(type.FullName);
if (result != null) break;
}
if (result == null && demand)
{
throw new InvalidOperationException("Unable to map type: " + type.AssemblyQualifiedName);
}
}
return result;
}
#endif
///
/// Represents configuration options for compiling a model to
/// a standalone assembly.
///
public sealed class CompilerOptions
{
///
/// Import framework options from an existing type
///
public void SetFrameworkOptions(MetaType from)
{
if (from == null) throw new ArgumentNullException("from");
AttributeMap[] attribs = AttributeMap.Create(from.Model, Helpers.GetAssembly(from.Type));
foreach (AttributeMap attrib in attribs)
{
if (attrib.AttributeType.FullName == "System.Runtime.Versioning.TargetFrameworkAttribute")
{
object tmp;
if (attrib.TryGet("FrameworkName", out tmp)) TargetFrameworkName = (string)tmp;
if (attrib.TryGet("FrameworkDisplayName", out tmp)) TargetFrameworkDisplayName = (string)tmp;
break;
}
}
}
private string targetFrameworkName, targetFrameworkDisplayName, typeName, outputPath, imageRuntimeVersion;
private int metaDataVersion;
///
/// The TargetFrameworkAttribute FrameworkName value to burn into the generated assembly
///
public string TargetFrameworkName { get { return targetFrameworkName; } set { targetFrameworkName = value; } }
///
/// The TargetFrameworkAttribute FrameworkDisplayName value to burn into the generated assembly
///
public string TargetFrameworkDisplayName { get { return targetFrameworkDisplayName; } set { targetFrameworkDisplayName = value; } }
///
/// The name of the TypeModel class to create
///
public string TypeName { get { return typeName; } set { typeName = value; } }
#if COREFX
internal const string NoPersistence = "Assembly persistence not supported on this runtime";
#endif
///
/// The path for the new dll
///
#if COREFX
[Obsolete(NoPersistence)]
#endif
public string OutputPath { get { return outputPath; } set { outputPath = value; } }
///
/// The runtime version for the generated assembly
///
public string ImageRuntimeVersion { get { return imageRuntimeVersion; } set { imageRuntimeVersion = value; } }
///
/// The runtime version for the generated assembly
///
public int MetaDataVersion { get { return metaDataVersion; } set { metaDataVersion = value; } }
private Accessibility accessibility = Accessibility.Public;
///
/// The acecssibility of the generated serializer
///
public Accessibility Accessibility { get { return accessibility; } set { accessibility = value; } }
#if FEAT_IKVM
///
/// The name of the container that holds the key pair.
///
public string KeyContainer { get; set; }
///
/// The path to a file that hold the key pair.
///
public string KeyFile { get; set; }
///
/// The public key to sign the file with.
///
public string PublicKey { get; set; }
#endif
}
///
/// Type accessibility
///
public enum Accessibility
{
///
/// Available to all callers
///
Public,
///
/// Available to all callers in the same assembly, or assemblies specified via [InternalsVisibleTo(...)]
///
Internal
}
#if !COREFX
///
/// Fully compiles the current model into a static-compiled serialization dll
/// (the serialization dll still requires protobuf-net for support services).
///
/// A full compilation is restricted to accessing public types / members
/// The name of the TypeModel class to create
/// The path for the new dll
/// An instance of the newly created compiled type-model
public TypeModel Compile(string name, string path)
{
CompilerOptions options = new CompilerOptions();
options.TypeName = name;
options.OutputPath = path;
return Compile(options);
}
#endif
///
/// Fully compiles the current model into a static-compiled serialization dll
/// (the serialization dll still requires protobuf-net for support services).
///
/// A full compilation is restricted to accessing public types / members
/// An instance of the newly created compiled type-model
public TypeModel Compile(CompilerOptions options)
{
if (options == null) throw new ArgumentNullException("options");
string typeName = options.TypeName;
#pragma warning disable 0618
string path = options.OutputPath;
#pragma warning restore 0618
BuildAllSerializers();
Freeze();
bool save = !Helpers.IsNullOrEmpty(path);
if (Helpers.IsNullOrEmpty(typeName))
{
if (save) throw new ArgumentNullException("typeName");
typeName = Guid.NewGuid().ToString();
}
string assemblyName, moduleName;
if(path == null)
{
assemblyName = typeName;
moduleName = assemblyName + ".dll";
}
else
{
assemblyName = new System.IO.FileInfo(System.IO.Path.GetFileNameWithoutExtension(path)).Name;
moduleName = assemblyName + System.IO.Path.GetExtension(path);
}
#if FEAT_IKVM
IKVM.Reflection.AssemblyName an = new IKVM.Reflection.AssemblyName();
an.Name = assemblyName;
AssemblyBuilder asm = universe.DefineDynamicAssembly(an, AssemblyBuilderAccess.Save);
if (!Helpers.IsNullOrEmpty(options.KeyFile))
{
asm.__SetAssemblyKeyPair(new StrongNameKeyPair(File.OpenRead(options.KeyFile)));
}
else if (!Helpers.IsNullOrEmpty(options.KeyContainer))
{
asm.__SetAssemblyKeyPair(new StrongNameKeyPair(options.KeyContainer));
}
else if (!Helpers.IsNullOrEmpty(options.PublicKey))
{
asm.__SetAssemblyPublicKey(FromHex(options.PublicKey));
}
if(!Helpers.IsNullOrEmpty(options.ImageRuntimeVersion) && options.MetaDataVersion != 0)
{
asm.__SetImageRuntimeVersion(options.ImageRuntimeVersion, options.MetaDataVersion);
}
ModuleBuilder module = asm.DefineDynamicModule(moduleName, path);
#elif COREFX
AssemblyName an = new AssemblyName();
an.Name = assemblyName;
AssemblyBuilder asm = AssemblyBuilder.DefineDynamicAssembly(an,
AssemblyBuilderAccess.Run);
ModuleBuilder module = asm.DefineDynamicModule(moduleName);
#else
AssemblyName an = new AssemblyName();
an.Name = assemblyName;
AssemblyBuilder asm = AppDomain.CurrentDomain.DefineDynamicAssembly(an,
(save ? AssemblyBuilderAccess.RunAndSave : AssemblyBuilderAccess.Run)
);
ModuleBuilder module = save ? asm.DefineDynamicModule(moduleName, path)
: asm.DefineDynamicModule(moduleName);
#endif
WriteAssemblyAttributes(options, assemblyName, asm);
TypeBuilder type = WriteBasicTypeModel(options, typeName, module);
int index;
bool hasInheritance;
SerializerPair[] methodPairs;
Compiler.CompilerContext.ILVersion ilVersion;
WriteSerializers(options, assemblyName, type, out index, out hasInheritance, out methodPairs, out ilVersion);
ILGenerator il;
int knownTypesCategory;
FieldBuilder knownTypes;
Type knownTypesLookupType;
WriteGetKeyImpl(type, hasInheritance, methodPairs, ilVersion, assemblyName, out il, out knownTypesCategory, out knownTypes, out knownTypesLookupType);
// trivial flags
il = Override(type, "SerializeDateTimeKind");
il.Emit(IncludeDateTimeKind ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ret);
// end: trivial flags
Compiler.CompilerContext ctx = WriteSerializeDeserialize(assemblyName, type, methodPairs, ilVersion, ref il);
WriteConstructors(type, ref index, methodPairs, ref il, knownTypesCategory, knownTypes, knownTypesLookupType, ctx);
#if COREFX
Type finalType = type.CreateTypeInfo().AsType();
#else
Type finalType = type.CreateType();
#endif
if (!Helpers.IsNullOrEmpty(path))
{
#if COREFX
throw new NotSupportedException(CompilerOptions.NoPersistence);
#else
asm.Save(path);
Helpers.DebugWriteLine("Wrote dll:" + path);
#endif
}
#if FEAT_IKVM
return null;
#else
return (TypeModel)Activator.CreateInstance(finalType);
#endif
}
#if FEAT_IKVM
private byte[] FromHex(string value)
{
if (Helpers.IsNullOrEmpty(value)) throw new ArgumentNullException("value");
int len = value.Length / 2;
byte[] result = new byte[len];
for(int i = 0 ; i < len ; i++)
{
result[i] = Convert.ToByte(value.Substring(i * 2, 2), 16);
}
return result;
}
#endif
private void WriteConstructors(TypeBuilder type, ref int index, SerializerPair[] methodPairs, ref ILGenerator il, int knownTypesCategory, FieldBuilder knownTypes, Type knownTypesLookupType, Compiler.CompilerContext ctx)
{
type.DefineDefaultConstructor(MethodAttributes.Public);
il = type.DefineTypeInitializer().GetILGenerator();
switch (knownTypesCategory)
{
case KnownTypes_Array:
{
Compiler.CompilerContext.LoadValue(il, types.Count);
il.Emit(OpCodes.Newarr, ctx.MapType(typeof(System.Type)));
index = 0;
foreach (SerializerPair pair in methodPairs)
{
il.Emit(OpCodes.Dup);
Compiler.CompilerContext.LoadValue(il, index);
il.Emit(OpCodes.Ldtoken, pair.Type.Type);
il.EmitCall(OpCodes.Call, ctx.MapType(typeof(System.Type)).GetMethod("GetTypeFromHandle"), null);
il.Emit(OpCodes.Stelem_Ref);
index++;
}
il.Emit(OpCodes.Stsfld, knownTypes);
il.Emit(OpCodes.Ret);
}
break;
case KnownTypes_Dictionary:
{
Compiler.CompilerContext.LoadValue(il, types.Count);
//LocalBuilder loc = il.DeclareLocal(knownTypesLookupType);
il.Emit(OpCodes.Newobj, knownTypesLookupType.GetConstructor(new Type[] { MapType(typeof(int)) }));
il.Emit(OpCodes.Stsfld, knownTypes);
int typeIndex = 0;
foreach (SerializerPair pair in methodPairs)
{
il.Emit(OpCodes.Ldsfld, knownTypes);
il.Emit(OpCodes.Ldtoken, pair.Type.Type);
il.EmitCall(OpCodes.Call, ctx.MapType(typeof(System.Type)).GetMethod("GetTypeFromHandle"), null);
int keyIndex = typeIndex++, lastKey = pair.BaseKey;
if (lastKey != pair.MetaKey) // not a base-type; need to give the index of the base-type
{
keyIndex = -1; // assume epic fail
for (int j = 0; j < methodPairs.Length; j++)
{
if (methodPairs[j].BaseKey == lastKey && methodPairs[j].MetaKey == lastKey)
{
keyIndex = j;
break;
}
}
}
Compiler.CompilerContext.LoadValue(il, keyIndex);
il.EmitCall(OpCodes.Callvirt, knownTypesLookupType.GetMethod("Add", new Type[] { MapType(typeof(System.Type)), MapType(typeof(int)) }), null);
}
il.Emit(OpCodes.Ret);
}
break;
case KnownTypes_Hashtable:
{
Compiler.CompilerContext.LoadValue(il, types.Count);
il.Emit(OpCodes.Newobj, knownTypesLookupType.GetConstructor(new Type[] { MapType(typeof(int)) }));
il.Emit(OpCodes.Stsfld, knownTypes);
int typeIndex = 0;
foreach (SerializerPair pair in methodPairs)
{
il.Emit(OpCodes.Ldsfld, knownTypes);
il.Emit(OpCodes.Ldtoken, pair.Type.Type);
il.EmitCall(OpCodes.Call, ctx.MapType(typeof(System.Type)).GetMethod("GetTypeFromHandle"), null);
int keyIndex = typeIndex++, lastKey = pair.BaseKey;
if (lastKey != pair.MetaKey) // not a base-type; need to give the index of the base-type
{
keyIndex = -1; // assume epic fail
for (int j = 0; j < methodPairs.Length; j++)
{
if (methodPairs[j].BaseKey == lastKey && methodPairs[j].MetaKey == lastKey)
{
keyIndex = j;
break;
}
}
}
Compiler.CompilerContext.LoadValue(il, keyIndex);
il.Emit(OpCodes.Box, MapType(typeof(int)));
il.EmitCall(OpCodes.Callvirt, knownTypesLookupType.GetMethod("Add", new Type[] { MapType(typeof(object)), MapType(typeof(object)) }), null);
}
il.Emit(OpCodes.Ret);
}
break;
default:
throw new InvalidOperationException();
}
}
private Compiler.CompilerContext WriteSerializeDeserialize(string assemblyName, TypeBuilder type, SerializerPair[] methodPairs, Compiler.CompilerContext.ILVersion ilVersion, ref ILGenerator il)
{
il = Override(type, "Serialize");
Compiler.CompilerContext ctx = new Compiler.CompilerContext(il, false, true, methodPairs, this, ilVersion, assemblyName, MapType(typeof(object)), "Serialize " + type.Name);
// arg0 = this, arg1 = key, arg2=obj, arg3=dest
Compiler.CodeLabel[] jumpTable = new Compiler.CodeLabel[types.Count];
for (int i = 0; i < jumpTable.Length; i++)
{
jumpTable[i] = ctx.DefineLabel();
}
il.Emit(OpCodes.Ldarg_1);
ctx.Switch(jumpTable);
ctx.Return();
for (int i = 0; i < jumpTable.Length; i++)
{
SerializerPair pair = methodPairs[i];
ctx.MarkLabel(jumpTable[i]);
il.Emit(OpCodes.Ldarg_2);
ctx.CastFromObject(pair.Type.Type);
il.Emit(OpCodes.Ldarg_3);
il.EmitCall(OpCodes.Call, pair.Serialize, null);
ctx.Return();
}
il = Override(type, "Deserialize");
ctx = new Compiler.CompilerContext(il, false, false, methodPairs, this, ilVersion, assemblyName, MapType(typeof(object)), "Deserialize " + type.Name);
// arg0 = this, arg1 = key, arg2=obj, arg3=source
for (int i = 0; i < jumpTable.Length; i++)
{
jumpTable[i] = ctx.DefineLabel();
}
il.Emit(OpCodes.Ldarg_1);
ctx.Switch(jumpTable);
ctx.LoadNullRef();
ctx.Return();
for (int i = 0; i < jumpTable.Length; i++)
{
SerializerPair pair = methodPairs[i];
ctx.MarkLabel(jumpTable[i]);
Type keyType = pair.Type.Type;
if (Helpers.IsValueType(keyType))
{
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldarg_3);
il.EmitCall(OpCodes.Call, EmitBoxedSerializer(type, i, keyType, methodPairs, this, ilVersion, assemblyName), null);
ctx.Return();
}
else
{
il.Emit(OpCodes.Ldarg_2);
ctx.CastFromObject(keyType);
il.Emit(OpCodes.Ldarg_3);
il.EmitCall(OpCodes.Call, pair.Deserialize, null);
ctx.Return();
}
}
return ctx;
}
private const int KnownTypes_Array = 1, KnownTypes_Dictionary = 2, KnownTypes_Hashtable = 3, KnownTypes_ArrayCutoff = 20;
private void WriteGetKeyImpl(TypeBuilder type, bool hasInheritance, SerializerPair[] methodPairs, Compiler.CompilerContext.ILVersion ilVersion, string assemblyName, out ILGenerator il, out int knownTypesCategory, out FieldBuilder knownTypes, out Type knownTypesLookupType)
{
il = Override(type, "GetKeyImpl");
Compiler.CompilerContext ctx = new Compiler.CompilerContext(il, false, false, methodPairs, this, ilVersion, assemblyName, MapType(typeof(System.Type), true), "GetKeyImpl");
if (types.Count <= KnownTypes_ArrayCutoff)
{
knownTypesCategory = KnownTypes_Array;
knownTypesLookupType = MapType(typeof(System.Type[]), true);
}
else
{
#if NO_GENERICS
knownTypesLookupType = null;
#else
knownTypesLookupType = MapType(typeof(System.Collections.Generic.Dictionary), false);
#endif
#if !COREFX
if (knownTypesLookupType == null)
{
knownTypesLookupType = MapType(typeof(Hashtable), true);
knownTypesCategory = KnownTypes_Hashtable;
}
else
#endif
{
knownTypesCategory = KnownTypes_Dictionary;
}
}
knownTypes = type.DefineField("knownTypes", knownTypesLookupType, FieldAttributes.Private | FieldAttributes.InitOnly | FieldAttributes.Static);
switch (knownTypesCategory)
{
case KnownTypes_Array:
{
il.Emit(OpCodes.Ldsfld, knownTypes);
il.Emit(OpCodes.Ldarg_1);
// note that Array.IndexOf is not supported under CF
il.EmitCall(OpCodes.Callvirt, MapType(typeof(IList)).GetMethod(
"IndexOf", new Type[] { MapType(typeof(object)) }), null);
if (hasInheritance)
{
il.DeclareLocal(MapType(typeof(int))); // loc-0
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Stloc_0);
BasicList getKeyLabels = new BasicList();
int lastKey = -1;
for (int i = 0; i < methodPairs.Length; i++)
{
if (methodPairs[i].MetaKey == methodPairs[i].BaseKey) break;
if (lastKey == methodPairs[i].BaseKey)
{ // add the last label again
getKeyLabels.Add(getKeyLabels[getKeyLabels.Count - 1]);
}
else
{ // add a new unique label
getKeyLabels.Add(ctx.DefineLabel());
lastKey = methodPairs[i].BaseKey;
}
}
Compiler.CodeLabel[] subtypeLabels = new Compiler.CodeLabel[getKeyLabels.Count];
getKeyLabels.CopyTo(subtypeLabels, 0);
ctx.Switch(subtypeLabels);
il.Emit(OpCodes.Ldloc_0); // not a sub-type; use the original value
il.Emit(OpCodes.Ret);
lastKey = -1;
// now output the different branches per sub-type (not derived type)
for (int i = subtypeLabels.Length - 1; i >= 0; i--)
{
if (lastKey != methodPairs[i].BaseKey)
{
lastKey = methodPairs[i].BaseKey;
// find the actual base-index for this base-key (i.e. the index of
// the base-type)
int keyIndex = -1;
for (int j = subtypeLabels.Length; j < methodPairs.Length; j++)
{
if (methodPairs[j].BaseKey == lastKey && methodPairs[j].MetaKey == lastKey)
{
keyIndex = j;
break;
}
}
ctx.MarkLabel(subtypeLabels[i]);
Compiler.CompilerContext.LoadValue(il, keyIndex);
il.Emit(OpCodes.Ret);
}
}
}
else
{
il.Emit(OpCodes.Ret);
}
}
break;
case KnownTypes_Dictionary:
{
LocalBuilder result = il.DeclareLocal(MapType(typeof(int)));
Label otherwise = il.DefineLabel();
il.Emit(OpCodes.Ldsfld, knownTypes);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldloca_S, result);
il.EmitCall(OpCodes.Callvirt, knownTypesLookupType.GetMethod("TryGetValue", BindingFlags.Instance | BindingFlags.Public), null);
il.Emit(OpCodes.Brfalse_S, otherwise);
il.Emit(OpCodes.Ldloc_S, result);
il.Emit(OpCodes.Ret);
il.MarkLabel(otherwise);
il.Emit(OpCodes.Ldc_I4_M1);
il.Emit(OpCodes.Ret);
}
break;
case KnownTypes_Hashtable:
{
Label otherwise = il.DefineLabel();
il.Emit(OpCodes.Ldsfld, knownTypes);
il.Emit(OpCodes.Ldarg_1);
il.EmitCall(OpCodes.Callvirt, knownTypesLookupType.GetProperty("Item").GetGetMethod(), null);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Brfalse_S, otherwise);
#if FX11
il.Emit(OpCodes.Unbox, MapType(typeof(int)));
il.Emit(OpCodes.Ldobj, MapType(typeof(int)));
#else
if (ilVersion == Compiler.CompilerContext.ILVersion.Net1)
{
il.Emit(OpCodes.Unbox, MapType(typeof(int)));
il.Emit(OpCodes.Ldobj, MapType(typeof(int)));
}
else
{
il.Emit(OpCodes.Unbox_Any, MapType(typeof(int)));
}
#endif
il.Emit(OpCodes.Ret);
il.MarkLabel(otherwise);
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldc_I4_M1);
il.Emit(OpCodes.Ret);
}
break;
default:
throw new InvalidOperationException();
}
}
private void WriteSerializers(CompilerOptions options, string assemblyName, TypeBuilder type, out int index, out bool hasInheritance, out SerializerPair[] methodPairs, out Compiler.CompilerContext.ILVersion ilVersion)
{
Compiler.CompilerContext ctx;
index = 0;
hasInheritance = false;
methodPairs = new SerializerPair[types.Count];
foreach (MetaType metaType in types)
{
MethodBuilder writeMethod = type.DefineMethod("Write"
#if DEBUG
+ metaType.Type.Name
#endif
,
MethodAttributes.Private | MethodAttributes.Static, CallingConventions.Standard,
MapType(typeof(void)), new Type[] { metaType.Type, MapType(typeof(ProtoWriter)) });
MethodBuilder readMethod = type.DefineMethod("Read"
#if DEBUG
+ metaType.Type.Name
#endif
,
MethodAttributes.Private | MethodAttributes.Static, CallingConventions.Standard,
metaType.Type, new Type[] { metaType.Type, MapType(typeof(ProtoReader)) });
SerializerPair pair = new SerializerPair(
GetKey(metaType.Type, true, false), GetKey(metaType.Type, true, true), metaType,
writeMethod, readMethod, writeMethod.GetILGenerator(), readMethod.GetILGenerator());
methodPairs[index++] = pair;
if (pair.MetaKey != pair.BaseKey) hasInheritance = true;
}
if (hasInheritance)
{
Array.Sort(methodPairs);
}
ilVersion = Compiler.CompilerContext.ILVersion.Net2;
if (options.MetaDataVersion == 0x10000)
{
ilVersion = Compiler.CompilerContext.ILVersion.Net1; // old-school!
}
for (index = 0; index < methodPairs.Length; index++)
{
SerializerPair pair = methodPairs[index];
ctx = new Compiler.CompilerContext(pair.SerializeBody, true, true, methodPairs, this, ilVersion, assemblyName, pair.Type.Type, "SerializeImpl " + pair.Type.Type.Name);
ctx.CheckAccessibility(pair.Deserialize.ReturnType
#if COREFX
.GetTypeInfo()
#endif
);
pair.Type.Serializer.EmitWrite(ctx, ctx.InputValue);
ctx.Return();
ctx = new Compiler.CompilerContext(pair.DeserializeBody, true, false, methodPairs, this, ilVersion, assemblyName, pair.Type.Type, "DeserializeImpl " + pair.Type.Type.Name);
pair.Type.Serializer.EmitRead(ctx, ctx.InputValue);
if (!pair.Type.Serializer.ReturnsValue)
{
ctx.LoadValue(ctx.InputValue);
}
ctx.Return();
}
}
private TypeBuilder WriteBasicTypeModel(CompilerOptions options, string typeName, ModuleBuilder module)
{
Type baseType = MapType(typeof(TypeModel));
#if COREFX
TypeAttributes typeAttributes = (baseType.GetTypeInfo().Attributes & ~TypeAttributes.Abstract) | TypeAttributes.Sealed;
#else
TypeAttributes typeAttributes = (baseType.Attributes & ~TypeAttributes.Abstract) | TypeAttributes.Sealed;
#endif
if (options.Accessibility == Accessibility.Internal)
{
typeAttributes &= ~TypeAttributes.Public;
}
TypeBuilder type = module.DefineType(typeName, typeAttributes, baseType);
return type;
}
private void WriteAssemblyAttributes(CompilerOptions options, string assemblyName, AssemblyBuilder asm)
{
if (!Helpers.IsNullOrEmpty(options.TargetFrameworkName))
{
// get [TargetFramework] from mscorlib/equivalent and burn into the new assembly
Type versionAttribType = null;
try
{ // this is best-endeavours only
versionAttribType = GetType("System.Runtime.Versioning.TargetFrameworkAttribute", Helpers.GetAssembly(MapType(typeof(string))));
}
catch { /* don't stress */ }
if (versionAttribType != null)
{
PropertyInfo[] props;
object[] propValues;
if (Helpers.IsNullOrEmpty(options.TargetFrameworkDisplayName))
{
props = new PropertyInfo[0];
propValues = new object[0];
}
else
{
props = new PropertyInfo[1] { versionAttribType.GetProperty("FrameworkDisplayName") };
propValues = new object[1] { options.TargetFrameworkDisplayName };
}
CustomAttributeBuilder builder = new CustomAttributeBuilder(
versionAttribType.GetConstructor(new Type[] { MapType(typeof(string)) }),
new object[] { options.TargetFrameworkName },
props,
propValues);
asm.SetCustomAttribute(builder);
}
}
// copy assembly:InternalsVisibleTo
Type internalsVisibleToAttribType = null;
#if !FX11
try
{
internalsVisibleToAttribType = MapType(typeof(System.Runtime.CompilerServices.InternalsVisibleToAttribute));
}
catch { /* best endeavors only */ }
#endif
if (internalsVisibleToAttribType != null)
{
BasicList internalAssemblies = new BasicList(), consideredAssemblies = new BasicList();
foreach (MetaType metaType in types)
{
Assembly assembly = Helpers.GetAssembly(metaType.Type);
if (consideredAssemblies.IndexOfReference(assembly) >= 0) continue;
consideredAssemblies.Add(assembly);
AttributeMap[] assemblyAttribsMap = AttributeMap.Create(this, assembly);
for (int i = 0; i < assemblyAttribsMap.Length; i++)
{
if (assemblyAttribsMap[i].AttributeType != internalsVisibleToAttribType) continue;
object privelegedAssemblyObj;
assemblyAttribsMap[i].TryGet("AssemblyName", out privelegedAssemblyObj);
string privelegedAssemblyName = privelegedAssemblyObj as string;
if (privelegedAssemblyName == assemblyName || Helpers.IsNullOrEmpty(privelegedAssemblyName)) continue; // ignore
if (internalAssemblies.IndexOfString(privelegedAssemblyName) >= 0) continue; // seen it before
internalAssemblies.Add(privelegedAssemblyName);
CustomAttributeBuilder builder = new CustomAttributeBuilder(
internalsVisibleToAttribType.GetConstructor(new Type[] { MapType(typeof(string)) }),
new object[] { privelegedAssemblyName });
asm.SetCustomAttribute(builder);
}
}
}
}
private static MethodBuilder EmitBoxedSerializer(TypeBuilder type, int i, Type valueType, SerializerPair[] methodPairs, TypeModel model, Compiler.CompilerContext.ILVersion ilVersion, string assemblyName)
{
MethodInfo dedicated = methodPairs[i].Deserialize;
MethodBuilder boxedSerializer = type.DefineMethod("_" + i.ToString(), MethodAttributes.Static, CallingConventions.Standard,
model.MapType(typeof(object)), new Type[] { model.MapType(typeof(object)), model.MapType(typeof(ProtoReader)) });
Compiler.CompilerContext ctx = new Compiler.CompilerContext(boxedSerializer.GetILGenerator(), true, false, methodPairs, model, ilVersion, assemblyName, model.MapType(typeof(object)), "BoxedSerializer " + valueType.Name);
ctx.LoadValue(ctx.InputValue);
Compiler.CodeLabel @null = ctx.DefineLabel();
ctx.BranchIfFalse(@null, true);
Type mappedValueType = valueType;
ctx.LoadValue(ctx.InputValue);
ctx.CastFromObject(mappedValueType);
ctx.LoadReaderWriter();
ctx.EmitCall(dedicated);
ctx.CastToObject(mappedValueType);
ctx.Return();
ctx.MarkLabel(@null);
using (Compiler.Local typedVal = new Compiler.Local(ctx, mappedValueType))
{
// create a new valueType
ctx.LoadAddress(typedVal, mappedValueType);
ctx.EmitCtor(mappedValueType);
ctx.LoadValue(typedVal);
ctx.LoadReaderWriter();
ctx.EmitCall(dedicated);
ctx.CastToObject(mappedValueType);
ctx.Return();
}
return boxedSerializer;
}
#endif
#endif
//internal bool IsDefined(Type type, int fieldNumber)
//{
// return FindWithoutAdd(type).IsDefined(fieldNumber);
//}
// note that this is used by some of the unit tests
internal bool IsPrepared(Type type)
{
MetaType meta = FindWithoutAdd(type);
return meta != null && meta.IsPrepared();
}
internal EnumSerializer.EnumPair[] GetEnumMap(Type type)
{
int index = FindOrAddAuto(type, false, false, false);
return index < 0 ? null : ((MetaType)types[index]).GetEnumMap();
}
private int metadataTimeoutMilliseconds = 5000;
///
/// The amount of time to wait if there are concurrent metadata access operations
///
public int MetadataTimeoutMilliseconds
{
get { return metadataTimeoutMilliseconds; }
set
{
if (value <= 0) throw new ArgumentOutOfRangeException("MetadataTimeoutMilliseconds");
metadataTimeoutMilliseconds = value;
}
}
#if DEBUG
int lockCount;
///
/// Gets how many times a model lock was taken
///
public int LockCount { get { return lockCount; } }
#endif
internal void TakeLock(ref int opaqueToken)
{
const string message = "Timeout while inspecting metadata; this may indicate a deadlock. This can often be avoided by preparing necessary serializers during application initialization, rather than allowing multiple threads to perform the initial metadata inspection; please also see the LockContended event";
opaqueToken = 0;
#if PORTABLE
if(!Monitor.TryEnter(types, metadataTimeoutMilliseconds)) throw new TimeoutException(message);
opaqueToken = Interlocked.CompareExchange(ref contentionCounter, 0, 0); // just fetch current value (starts at 1)
#elif CF2 || CF35
int remaining = metadataTimeoutMilliseconds;
bool lockTaken;
do {
lockTaken = Monitor.TryEnter(types);
if(!lockTaken)
{
if(remaining <= 0) throw new TimeoutException(message);
remaining -= 50;
Thread.Sleep(50);
}
} while(!lockTaken);
opaqueToken = Interlocked.CompareExchange(ref contentionCounter, 0, 0); // just fetch current value (starts at 1)
#else
if (Monitor.TryEnter(types, metadataTimeoutMilliseconds))
{
opaqueToken = GetContention(); // just fetch current value (starts at 1)
}
else
{
AddContention();
#if FX11
throw new InvalidOperationException(message);
#else
throw new TimeoutException(message);
#endif
}
#endif
#if DEBUG // note that here, through all code-paths: we have the lock
lockCount++;
#endif
}
private int contentionCounter = 1;
#if PLAT_NO_INTERLOCKED
private readonly object contentionLock = new object();
#endif
private int GetContention()
{
#if PLAT_NO_INTERLOCKED
lock(contentionLock)
{
return contentionCounter;
}
#else
return Interlocked.CompareExchange(ref contentionCounter, 0, 0);
#endif
}
private void AddContention()
{
#if PLAT_NO_INTERLOCKED
lock(contentionLock)
{
contentionCounter++;
}
#else
Interlocked.Increment(ref contentionCounter);
#endif
}
internal void ReleaseLock(int opaqueToken)
{
if (opaqueToken != 0)
{
Monitor.Exit(types);
if(opaqueToken != GetContention()) // contention-count changes since we looked!
{
LockContentedEventHandler handler = LockContended;
if (handler != null)
{
// not hugely elegant, but this is such a far-corner-case that it doesn't need to be slick - I'll settle for cross-platform
string stackTrace;
try
{
throw new ProtoException();
}
catch(Exception ex)
{
stackTrace = ex.StackTrace;
}
handler(this, new LockContentedEventArgs(stackTrace));
}
}
}
}
///
/// If a lock-contention is detected, this event signals the *owner* of the lock responsible for the blockage, indicating
/// what caused the problem; this is only raised if the lock-owning code successfully completes.
///
public event LockContentedEventHandler LockContended;
internal void ResolveListTypes(Type type, ref Type itemType, ref Type defaultType)
{
if (type == null) return;
if(Helpers.GetTypeCode(type) != ProtoTypeCode.Unknown) return; // don't try this[type] for inbuilts
if(this[type].IgnoreListHandling) return;
// handle arrays
if (type.IsArray)
{
if (type.GetArrayRank() != 1)
{
throw new NotSupportedException("Multi-dimension arrays are supported");
}
itemType = type.GetElementType();
if (itemType == MapType(typeof(byte)))
{
defaultType = itemType = null;
}
else
{
defaultType = type;
}
}
// handle lists
if (itemType == null) { itemType = TypeModel.GetListItemType(this, type); }
// check for nested data (not allowed)
if (itemType != null)
{
Type nestedItemType = null, nestedDefaultType = null;
ResolveListTypes(itemType, ref nestedItemType, ref nestedDefaultType);
if (nestedItemType != null)
{
throw TypeModel.CreateNestedListsNotSupported();
}
}
if (itemType != null && defaultType == null)
{
#if WINRT || COREFX
System.Reflection.TypeInfo typeInfo = System.Reflection.IntrospectionExtensions.GetTypeInfo(type);
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 && typeInfo.GetGenericTypeDefinition() == typeof(System.Collections.Generic.IDictionary<,>)
&& itemType == typeof(System.Collections.Generic.KeyValuePair<,>).MakeGenericType(genArgs = typeInfo.GenericTypeArguments))
#else
if (type.IsGenericType && type.GetGenericTypeDefinition() == MapType(typeof(System.Collections.Generic.IDictionary<,>))
&& itemType == MapType(typeof(System.Collections.Generic.KeyValuePair<,>)).MakeGenericType(genArgs = type.GetGenericArguments()))
#endif
{
defaultType = MapType(typeof(System.Collections.Generic.Dictionary<,>)).MakeGenericType(genArgs);
}
else
{
defaultType = 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; }
}
}
#if FEAT_IKVM
internal override Type GetType(string fullName, Assembly context)
{
if (context != null)
{
Type found = universe.GetType(context, fullName, false);
if (found != null) return found;
}
return universe.GetType(fullName, false);
}
#endif
internal string GetSchemaTypeName(Type effectiveType, DataFormat dataFormat, bool asReference, bool dynamicType, ref bool requiresBclImport)
{
Type tmp = Helpers.GetUnderlyingType(effectiveType);
if (tmp != null) effectiveType = tmp;
if (effectiveType == this.MapType(typeof(byte[]))) return "bytes";
WireType wireType;
IProtoSerializer ser = ValueMember.TryGetCoreSerializer(this, dataFormat, effectiveType, out wireType, false, false, false, false);
if (ser == null)
{ // model type
if (asReference || dynamicType)
{
requiresBclImport = true;
return "bcl.NetObjectProxy";
}
return this[effectiveType].GetSurrogateOrBaseOrSelf(true).GetSchemaTypeName();
}
else
{
if (ser is ParseableSerializer)
{
if (asReference) requiresBclImport = true;
return asReference ? "bcl.NetObjectProxy" : "string";
}
switch (Helpers.GetTypeCode(effectiveType))
{
case ProtoTypeCode.Boolean: return "bool";
case ProtoTypeCode.Single: return "float";
case ProtoTypeCode.Double: return "double";
case ProtoTypeCode.String:
if (asReference) requiresBclImport = true;
return asReference ? "bcl.NetObjectProxy" : "string";
case ProtoTypeCode.Byte:
case ProtoTypeCode.Char:
case ProtoTypeCode.UInt16:
case ProtoTypeCode.UInt32:
switch (dataFormat)
{
case DataFormat.FixedSize: return "fixed32";
default: return "uint32";
}
case ProtoTypeCode.SByte:
case ProtoTypeCode.Int16:
case ProtoTypeCode.Int32:
switch (dataFormat)
{
case DataFormat.ZigZag: return "sint32";
case DataFormat.FixedSize: return "sfixed32";
default: return "int32";
}
case ProtoTypeCode.UInt64:
switch (dataFormat)
{
case DataFormat.FixedSize: return "fixed64";
default: return "uint64";
}
case ProtoTypeCode.Int64:
switch (dataFormat)
{
case DataFormat.ZigZag: return "sint64";
case DataFormat.FixedSize: return "sfixed64";
default: return "int64";
}
case ProtoTypeCode.DateTime: requiresBclImport = true; return "bcl.DateTime";
case ProtoTypeCode.TimeSpan: requiresBclImport = true; return "bcl.TimeSpan";
case ProtoTypeCode.Decimal: requiresBclImport = true; return "bcl.Decimal";
case ProtoTypeCode.Guid: requiresBclImport = true; return "bcl.Guid";
default: throw new NotSupportedException("No .proto map found for: " + effectiveType.FullName);
}
}
}
///
/// Designate a factory-method to use to create instances of any type; note that this only affect types seen by the serializer *after* setting the factory.
///
public void SetDefaultFactory(MethodInfo methodInfo)
{
VerifyFactory(methodInfo, null);
defaultFactory = methodInfo;
}
private MethodInfo defaultFactory;
internal void VerifyFactory(MethodInfo factory, Type type)
{
if (factory != null)
{
if (type != null && Helpers.IsValueType(type)) throw new InvalidOperationException();
if (!factory.IsStatic) throw new ArgumentException("A factory-method must be static", "factory");
if ((type != null && factory.ReturnType != type) && factory.ReturnType != MapType(typeof(object))) throw new ArgumentException("The factory-method must return object" + (type == null ? "" : (" or " + type.FullName)), "factory");
if (!CallbackSet.CheckCallbackParameters(this, factory)) throw new ArgumentException("Invalid factory signature in " + factory.DeclaringType.FullName + "." + factory.Name, "factory");
}
}
}
///
/// Contains the stack-trace of the owning code when a lock-contention scenario is detected
///
public sealed class LockContentedEventArgs : EventArgs
{
private readonly string ownerStackTrace;
internal LockContentedEventArgs(string ownerStackTrace)
{
this.ownerStackTrace = ownerStackTrace;
}
///
/// The stack-trace of the code that owned the lock when a lock-contention scenario occurred
///
public string OwnerStackTrace { get { return ownerStackTrace; } }
}
///
/// Event-type that is raised when a lock-contention scenario is detected
///
public delegate void LockContentedEventHandler(object sender, LockContentedEventArgs args);
}
#endif