using System; using System.IO; using System.Collections; #if FEAT_IKVM using Type = IKVM.Reflection.Type; using IKVM.Reflection; #else using System.Reflection; #endif namespace ProtoBuf.Meta { /// /// Provides protobuf serialization support for a number of types /// public abstract class TypeModel { #if WINRT || COREFX internal TypeInfo MapType(TypeInfo type) { return type; } #endif /// /// Should the Kind be included on date/time values? /// protected internal virtual bool SerializeDateTimeKind() { return false; } /// /// Resolve a System.Type to the compiler-specific type /// protected internal Type MapType(System.Type type) { return MapType(type, true); } /// /// Resolve a System.Type to the compiler-specific type /// protected internal virtual Type MapType(System.Type type, bool demand) { #if FEAT_IKVM throw new NotImplementedException(); // this should come from RuntimeTypeModel! #else return type; #endif } private WireType GetWireType(ProtoTypeCode code, DataFormat format, ref Type type, out int modelKey) { modelKey = -1; if (Helpers.IsEnum(type)) { modelKey = GetKey(ref type); return WireType.Variant; } switch (code) { case ProtoTypeCode.Int64: case ProtoTypeCode.UInt64: return format == DataFormat.FixedSize ? WireType.Fixed64 : WireType.Variant; case ProtoTypeCode.Int16: case ProtoTypeCode.Int32: case ProtoTypeCode.UInt16: case ProtoTypeCode.UInt32: case ProtoTypeCode.Boolean: case ProtoTypeCode.SByte: case ProtoTypeCode.Byte: case ProtoTypeCode.Char: return format == DataFormat.FixedSize ? WireType.Fixed32 : WireType.Variant; case ProtoTypeCode.Double: return WireType.Fixed64; case ProtoTypeCode.Single: return WireType.Fixed32; case ProtoTypeCode.String: case ProtoTypeCode.DateTime: case ProtoTypeCode.Decimal: case ProtoTypeCode.ByteArray: case ProtoTypeCode.TimeSpan: case ProtoTypeCode.Guid: case ProtoTypeCode.Uri: return WireType.String; } if ((modelKey = GetKey(ref type)) >= 0) { return WireType.String; } return WireType.None; } #if !FEAT_IKVM /// /// This is the more "complete" version of Serialize, which handles single instances of mapped types. /// The value is written as a complete field, including field-header and (for sub-objects) a /// length-prefix /// In addition to that, this provides support for: /// - basic values; individual int / string / Guid / etc /// - IEnumerable sequences of any type handled by TrySerializeAuxiliaryType /// /// internal bool TrySerializeAuxiliaryType(ProtoWriter writer, Type type, DataFormat format, int tag, object value, bool isInsideList) { if (type == null) { type = value.GetType(); } ProtoTypeCode typecode = Helpers.GetTypeCode(type); int modelKey; // note the "ref type" here normalizes against proxies WireType wireType = GetWireType(typecode, format, ref type, out modelKey); if (modelKey >= 0) { // write the header, but defer to the model if (Helpers.IsEnum(type)) { // no header Serialize(modelKey, value, writer); return true; } else { ProtoWriter.WriteFieldHeader(tag, wireType, writer); switch (wireType) { case WireType.None: throw ProtoWriter.CreateException(writer); case WireType.StartGroup: case WireType.String: // needs a wrapping length etc SubItemToken token = ProtoWriter.StartSubItem(value, writer); Serialize(modelKey, value, writer); ProtoWriter.EndSubItem(token, writer); return true; default: Serialize(modelKey, value, writer); return true; } } } if(wireType != WireType.None) { ProtoWriter.WriteFieldHeader(tag, wireType, writer); } switch(typecode) { case ProtoTypeCode.Int16: ProtoWriter.WriteInt16((short)value, writer); return true; case ProtoTypeCode.Int32: ProtoWriter.WriteInt32((int)value, writer); return true; case ProtoTypeCode.Int64: ProtoWriter.WriteInt64((long)value, writer); return true; case ProtoTypeCode.UInt16: ProtoWriter.WriteUInt16((ushort)value, writer); return true; case ProtoTypeCode.UInt32: ProtoWriter.WriteUInt32((uint)value, writer); return true; case ProtoTypeCode.UInt64: ProtoWriter.WriteUInt64((ulong)value, writer); return true; case ProtoTypeCode.Boolean: ProtoWriter.WriteBoolean((bool)value, writer); return true; case ProtoTypeCode.SByte: ProtoWriter.WriteSByte((sbyte)value, writer); return true; case ProtoTypeCode.Byte: ProtoWriter.WriteByte((byte)value, writer); return true; case ProtoTypeCode.Char: ProtoWriter.WriteUInt16((ushort)(char)value, writer); return true; case ProtoTypeCode.Double: ProtoWriter.WriteDouble((double)value, writer); return true; case ProtoTypeCode.Single: ProtoWriter.WriteSingle((float)value, writer); return true; case ProtoTypeCode.DateTime: if (SerializeDateTimeKind()) BclHelpers.WriteDateTimeWithKind((DateTime)value, writer); else BclHelpers.WriteDateTime((DateTime)value, writer); return true; case ProtoTypeCode.Decimal: BclHelpers.WriteDecimal((decimal)value, writer); return true; case ProtoTypeCode.String: ProtoWriter.WriteString((string)value, writer); return true; case ProtoTypeCode.ByteArray: ProtoWriter.WriteBytes((byte[])value, writer); return true; case ProtoTypeCode.TimeSpan: BclHelpers.WriteTimeSpan((TimeSpan)value, writer); return true; case ProtoTypeCode.Guid: BclHelpers.WriteGuid((Guid)value, writer); return true; case ProtoTypeCode.Uri: ProtoWriter.WriteString(((Uri)value).AbsoluteUri, writer); return true; } // by now, we should have covered all the simple cases; if we wrote a field-header, we have // forgotten something! Helpers.DebugAssert(wireType == WireType.None); // now attempt to handle sequences (including arrays and lists) IEnumerable sequence = value as IEnumerable; if (sequence != null) { if (isInsideList) throw CreateNestedListsNotSupported(); foreach (object item in sequence) { if (item == null) { throw new NullReferenceException(); } if (!TrySerializeAuxiliaryType(writer, null, format, tag, item, true)) { ThrowUnexpectedType(item.GetType()); } } return true; } return false; } private void SerializeCore(ProtoWriter writer, object value) { if (value == null) throw new ArgumentNullException("value"); Type type = value.GetType(); int key = GetKey(ref type); if (key >= 0) { Serialize(key, value, writer); } else if (!TrySerializeAuxiliaryType(writer, type, DataFormat.Default, Serializer.ListItemTag, value, false)) { ThrowUnexpectedType(type); } } #endif /// /// Writes a protocol-buffer representation of the given instance to the supplied stream. /// /// The existing instance to be serialized (cannot be null). /// The destination stream to write to. public void Serialize(Stream dest, object value) { Serialize(dest, value, null); } /// /// Writes a protocol-buffer representation of the given instance to the supplied stream. /// /// The existing instance to be serialized (cannot be null). /// The destination stream to write to. /// Additional information about this serialization operation. public void Serialize(Stream dest, object value, SerializationContext context) { #if FEAT_IKVM throw new NotSupportedException(); #else using (ProtoWriter writer = new ProtoWriter(dest, this, context)) { writer.SetRootObject(value); SerializeCore(writer, value); writer.Close(); } #endif } /// /// Writes a protocol-buffer representation of the given instance to the supplied writer. /// /// The existing instance to be serialized (cannot be null). /// The destination writer to write to. public void Serialize(ProtoWriter dest, object value) { #if FEAT_IKVM throw new NotSupportedException(); #else if (dest == null) throw new ArgumentNullException("dest"); dest.CheckDepthFlushlock(); dest.SetRootObject(value); SerializeCore(dest, value); dest.CheckDepthFlushlock(); ProtoWriter.Flush(dest); #endif } /// /// Applies a protocol-buffer stream to an existing instance (or null), using length-prefixed /// data - useful with network IO. /// /// The type being merged. /// The existing instance to be modified (can be null). /// The binary stream to apply to the instance (cannot be null). /// How to encode the length prefix. /// The tag used as a prefix to each record (only used with base-128 style prefixes). /// 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. public object DeserializeWithLengthPrefix(Stream source, object value, Type type, PrefixStyle style, int fieldNumber) { int bytesRead; return DeserializeWithLengthPrefix(source, value, type, style, fieldNumber, null, out bytesRead); } /// /// Applies a protocol-buffer stream to an existing instance (or null), using length-prefixed /// data - useful with network IO. /// /// The type being merged. /// The existing instance to be modified (can be null). /// The binary stream to apply to the instance (cannot be null). /// How to encode the length prefix. /// The tag used as a prefix to each record (only used with base-128 style prefixes). /// Used to resolve types on a per-field basis. /// 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. public object DeserializeWithLengthPrefix(Stream source, object value, Type type, PrefixStyle style, int expectedField, Serializer.TypeResolver resolver) { int bytesRead; return DeserializeWithLengthPrefix(source, value, type, style, expectedField, resolver, out bytesRead); } /// /// Applies a protocol-buffer stream to an existing instance (or null), using length-prefixed /// data - useful with network IO. /// /// The type being merged. /// The existing instance to be modified (can be null). /// The binary stream to apply to the instance (cannot be null). /// How to encode the length prefix. /// The tag used as a prefix to each record (only used with base-128 style prefixes). /// Used to resolve types on a per-field basis. /// Returns the number of bytes consumed by this operation (includes length-prefix overheads and any skipped data). /// 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. public object DeserializeWithLengthPrefix(Stream source, object value, Type type, PrefixStyle style, int expectedField, Serializer.TypeResolver resolver, out int bytesRead) { bool haveObject; return DeserializeWithLengthPrefix(source, value, type, style, expectedField, resolver, out bytesRead, out haveObject, null); } private object DeserializeWithLengthPrefix(Stream source, object value, Type type, PrefixStyle style, int expectedField, Serializer.TypeResolver resolver, out int bytesRead, out bool haveObject, SerializationContext context) { #if FEAT_IKVM throw new NotSupportedException(); #else haveObject = false; bool skip; int len; int tmpBytesRead; bytesRead = 0; if (type == null && (style != PrefixStyle.Base128 || resolver == null)) { throw new InvalidOperationException("A type must be provided unless base-128 prefixing is being used in combination with a resolver"); } int actualField; do { bool expectPrefix = expectedField > 0 || resolver != null; len = ProtoReader.ReadLengthPrefix(source, expectPrefix, style, out actualField, out tmpBytesRead); if (tmpBytesRead == 0) return value; bytesRead += tmpBytesRead; if (len < 0) return value; switch (style) { case PrefixStyle.Base128: if (expectPrefix && expectedField == 0 && type == null && resolver != null) { type = resolver(actualField); skip = type == null; } else { skip = expectedField != actualField; } break; default: skip = false; break; } if (skip) { if (len == int.MaxValue) throw new InvalidOperationException(); ProtoReader.Seek(source, len, null); bytesRead += len; } } while (skip); ProtoReader reader = null; try { reader = ProtoReader.Create(source, this, context, len); int key = GetKey(ref type); if (key >= 0 && !Helpers.IsEnum(type)) { value = Deserialize(key, value, reader); } else { if (!(TryDeserializeAuxiliaryType(reader, DataFormat.Default, Serializer.ListItemTag, type, ref value, true, false, true, false) || len == 0)) { TypeModel.ThrowUnexpectedType(type); // throws } } bytesRead += reader.Position; haveObject = true; return value; } finally { ProtoReader.Recycle(reader); } #endif } /// /// Reads a sequence of consecutive length-prefixed items from a stream, using /// either base-128 or fixed-length prefixes. Base-128 prefixes with a tag /// are directly comparable to serializing multiple items in succession /// (use the tag to emulate the implicit behavior /// when serializing a list/array). When a tag is /// specified, any records with different tags are silently omitted. The /// tag is ignored. The tag is ignores for fixed-length prefixes. /// /// The binary stream containing the serialized records. /// The prefix style used in the data. /// The tag of records to return (if non-positive, then no tag is /// expected and all records are returned). /// On a field-by-field basis, the type of object to deserialize (can be null if "type" is specified). /// The type of object to deserialize (can be null if "resolver" is specified). /// The sequence of deserialized objects. public System.Collections.IEnumerable DeserializeItems(System.IO.Stream source, Type type, PrefixStyle style, int expectedField, Serializer.TypeResolver resolver) { return DeserializeItems(source, type, style, expectedField, resolver, null); } /// /// Reads a sequence of consecutive length-prefixed items from a stream, using /// either base-128 or fixed-length prefixes. Base-128 prefixes with a tag /// are directly comparable to serializing multiple items in succession /// (use the tag to emulate the implicit behavior /// when serializing a list/array). When a tag is /// specified, any records with different tags are silently omitted. The /// tag is ignored. The tag is ignores for fixed-length prefixes. /// /// The binary stream containing the serialized records. /// The prefix style used in the data. /// The tag of records to return (if non-positive, then no tag is /// expected and all records are returned). /// On a field-by-field basis, the type of object to deserialize (can be null if "type" is specified). /// The type of object to deserialize (can be null if "resolver" is specified). /// The sequence of deserialized objects. /// Additional information about this serialization operation. public System.Collections.IEnumerable DeserializeItems(System.IO.Stream source, Type type, PrefixStyle style, int expectedField, Serializer.TypeResolver resolver, SerializationContext context) { return new DeserializeItemsIterator(this, source, type, style, expectedField, resolver, context); } #if !NO_GENERICS /// /// Reads a sequence of consecutive length-prefixed items from a stream, using /// either base-128 or fixed-length prefixes. Base-128 prefixes with a tag /// are directly comparable to serializing multiple items in succession /// (use the tag to emulate the implicit behavior /// when serializing a list/array). When a tag is /// specified, any records with different tags are silently omitted. The /// tag is ignored. The tag is ignores for fixed-length prefixes. /// /// The type of object to deserialize. /// The binary stream containing the serialized records. /// The prefix style used in the data. /// The tag of records to return (if non-positive, then no tag is /// expected and all records are returned). /// The sequence of deserialized objects. public System.Collections.Generic.IEnumerable DeserializeItems(Stream source, PrefixStyle style, int expectedField) { return DeserializeItems(source, style, expectedField, null); } /// /// Reads a sequence of consecutive length-prefixed items from a stream, using /// either base-128 or fixed-length prefixes. Base-128 prefixes with a tag /// are directly comparable to serializing multiple items in succession /// (use the tag to emulate the implicit behavior /// when serializing a list/array). When a tag is /// specified, any records with different tags are silently omitted. The /// tag is ignored. The tag is ignores for fixed-length prefixes. /// /// The type of object to deserialize. /// The binary stream containing the serialized records. /// The prefix style used in the data. /// The tag of records to return (if non-positive, then no tag is /// expected and all records are returned). /// The sequence of deserialized objects. /// Additional information about this serialization operation. public System.Collections.Generic.IEnumerable DeserializeItems(Stream source, PrefixStyle style, int expectedField, SerializationContext context) { return new DeserializeItemsIterator(this, source, style, expectedField, context); } private sealed class DeserializeItemsIterator : DeserializeItemsIterator, System.Collections.Generic.IEnumerator, System.Collections.Generic.IEnumerable { System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { return this; } public new T Current { get { return (T)base.Current; } } void IDisposable.Dispose() { } public DeserializeItemsIterator(TypeModel model, Stream source, PrefixStyle style, int expectedField, SerializationContext context) : base(model, source, model.MapType(typeof(T)), style, expectedField, null, context) { } } #endif private class DeserializeItemsIterator : IEnumerator, IEnumerable { IEnumerator IEnumerable.GetEnumerator() { return this; } private bool haveObject; private object current; public bool MoveNext() { if (haveObject) { int bytesRead; current = model.DeserializeWithLengthPrefix(source, null, type, style, expectedField, resolver, out bytesRead, out haveObject, context); } return haveObject; } void IEnumerator.Reset() { throw new NotSupportedException(); } public object Current { get { return current; } } private readonly Stream source; private readonly Type type; private readonly PrefixStyle style; private readonly int expectedField; private readonly Serializer.TypeResolver resolver; private readonly TypeModel model; private readonly SerializationContext context; public DeserializeItemsIterator(TypeModel model, Stream source, Type type, PrefixStyle style, int expectedField, Serializer.TypeResolver resolver, SerializationContext context) { haveObject = true; this.source = source; this.type = type; this.style = style; this.expectedField = expectedField; this.resolver = resolver; this.model = model; this.context = context; } } /// /// Writes a protocol-buffer representation of the given instance to the supplied stream, /// with a length-prefix. This is useful for socket programming, /// as DeserializeWithLengthPrefix can be used to read the single object back /// from an ongoing stream. /// /// The type being serialized. /// The existing instance to be serialized (cannot be null). /// How to encode the length prefix. /// The destination stream to write to. /// The tag used as a prefix to each record (only used with base-128 style prefixes). public void SerializeWithLengthPrefix(Stream dest, object value, Type type, PrefixStyle style, int fieldNumber) { SerializeWithLengthPrefix(dest, value, type, style, fieldNumber, null); } /// /// Writes a protocol-buffer representation of the given instance to the supplied stream, /// with a length-prefix. This is useful for socket programming, /// as DeserializeWithLengthPrefix can be used to read the single object back /// from an ongoing stream. /// /// The type being serialized. /// The existing instance to be serialized (cannot be null). /// How to encode the length prefix. /// The destination stream to write to. /// The tag used as a prefix to each record (only used with base-128 style prefixes). /// Additional information about this serialization operation. public void SerializeWithLengthPrefix(Stream dest, object value, Type type, PrefixStyle style, int fieldNumber, SerializationContext context) { if (type == null) { if(value == null) throw new ArgumentNullException("value"); type = MapType(value.GetType()); } int key = GetKey(ref type); using (ProtoWriter writer = new ProtoWriter(dest, this, context)) { switch (style) { case PrefixStyle.None: Serialize(key, value, writer); break; case PrefixStyle.Base128: case PrefixStyle.Fixed32: case PrefixStyle.Fixed32BigEndian: ProtoWriter.WriteObject(value, key, writer, style, fieldNumber); break; default: throw new ArgumentOutOfRangeException("style"); } writer.Close(); } } /// /// Applies a protocol-buffer stream to an existing instance (which may be null). /// /// 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. public object Deserialize(Stream source, object value, System.Type type) { return Deserialize(source, value, type, null); } /// /// Applies a protocol-buffer stream to an existing instance (which may be null). /// /// 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. /// Additional information about this serialization operation. public object Deserialize(Stream source, object value, System.Type type, SerializationContext context) { #if FEAT_IKVM throw new NotSupportedException(); #else bool autoCreate = PrepareDeserialize(value, ref type); ProtoReader reader = null; try { reader = ProtoReader.Create(source, this, context, ProtoReader.TO_EOF); if (value != null) reader.SetRootObject(value); object obj = DeserializeCore(reader, type, value, autoCreate); reader.CheckFullyConsumed(); return obj; } finally { ProtoReader.Recycle(reader); } #endif } private bool PrepareDeserialize(object value, ref Type type) { if (type == null) { if (value == null) { throw new ArgumentNullException("type"); } else { type = MapType(value.GetType()); } } bool autoCreate = true; #if !NO_GENERICS Type underlyingType = Helpers.GetUnderlyingType(type); if (underlyingType != null) { type = underlyingType; autoCreate = false; } #endif return autoCreate; } /// /// Applies a protocol-buffer stream to an existing instance (which may be null). /// /// 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 number of bytes to consume. /// 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. public object Deserialize(Stream source, object value, System.Type type, int length) { return Deserialize(source, value, type, length, null); } /// /// Applies a protocol-buffer stream to an existing instance (which may be null). /// /// 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 number of bytes to consume (or -1 to read to the end of the stream). /// 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. /// Additional information about this serialization operation. public object Deserialize(Stream source, object value, System.Type type, int length, SerializationContext context) { #if FEAT_IKVM throw new NotSupportedException(); #else bool autoCreate = PrepareDeserialize(value, ref type); ProtoReader reader = null; try { reader = ProtoReader.Create(source, this, context, length); if (value != null) reader.SetRootObject(value); object obj = DeserializeCore(reader, type, value, autoCreate); reader.CheckFullyConsumed(); return obj; } finally { ProtoReader.Recycle(reader); } #endif } /// /// Applies a protocol-buffer reader to an existing instance (which may be null). /// /// The type (including inheritance) to consider. /// The existing instance to be modified (can be null). /// The reader 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. public object Deserialize(ProtoReader source, object value, System.Type type) { #if FEAT_IKVM throw new NotSupportedException(); #else if (source == null) throw new ArgumentNullException("source"); bool autoCreate = PrepareDeserialize(value, ref type); if (value != null) source.SetRootObject(value); object obj = DeserializeCore(source, type, value, autoCreate); source.CheckFullyConsumed(); return obj; #endif } #if !FEAT_IKVM private object DeserializeCore(ProtoReader reader, Type type, object value, bool noAutoCreate) { int key = GetKey(ref type); if (key >= 0 && !Helpers.IsEnum(type)) { return Deserialize(key, value, reader); } // this returns true to say we actively found something, but a value is assigned either way (or throws) TryDeserializeAuxiliaryType(reader, DataFormat.Default, Serializer.ListItemTag, type, ref value, true, false, noAutoCreate, false); return value; } #endif #if WINRT || COREFX private static readonly System.Reflection.TypeInfo ilist = typeof(IList).GetTypeInfo(); #else private static readonly System.Type ilist = typeof(IList); #endif internal static MethodInfo ResolveListAdd(TypeModel model, Type listType, Type itemType, out bool isList) { #if WINRT || COREFX TypeInfo listTypeInfo = listType.GetTypeInfo(); #else Type listTypeInfo = listType; #endif isList = model.MapType(ilist).IsAssignableFrom(listTypeInfo); Type[] types = { itemType }; MethodInfo add = Helpers.GetInstanceMethod(listTypeInfo, "Add", types); #if !NO_GENERICS if (add == null) { // fallback: look for ICollection's Add(typedObject) method bool forceList = listTypeInfo.IsInterface && listTypeInfo == model.MapType(typeof(System.Collections.Generic.IEnumerable<>)).MakeGenericType(types) #if WINRT || COREFX .GetTypeInfo() #endif ; #if WINRT || COREFX TypeInfo constuctedListType = typeof(System.Collections.Generic.ICollection<>).MakeGenericType(types).GetTypeInfo(); #else Type constuctedListType = model.MapType(typeof(System.Collections.Generic.ICollection<>)).MakeGenericType(types); #endif if (forceList || constuctedListType.IsAssignableFrom(listTypeInfo)) { add = Helpers.GetInstanceMethod(constuctedListType, "Add", types); } } if (add == null) { #if WINRT || COREFX foreach (Type tmpType in listTypeInfo.ImplementedInterfaces) #else foreach (Type interfaceType in listTypeInfo.GetInterfaces()) #endif { #if WINRT || COREFX TypeInfo interfaceType = tmpType.GetTypeInfo(); #endif if (interfaceType.Name == "IProducerConsumerCollection`1" && interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition().FullName == "System.Collections.Concurrent.IProducerConsumerCollection`1") { add = Helpers.GetInstanceMethod(interfaceType, "TryAdd", types); if (add != null) break; } } } #endif if (add == null) { // fallback: look for a public list.Add(object) method types[0] = model.MapType(typeof(object)); add = Helpers.GetInstanceMethod(listTypeInfo, "Add", types); } if (add == null && isList) { // fallback: look for IList's Add(object) method add = Helpers.GetInstanceMethod(model.MapType(ilist), "Add", types); } return add; } internal static Type GetListItemType(TypeModel model, Type listType) { Helpers.DebugAssert(listType != null); #if WINRT TypeInfo listTypeInfo = listType.GetTypeInfo(); if (listType == typeof(string) || listType.IsArray || !typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(listTypeInfo)) return null; #else if (listType == model.MapType(typeof(string)) || listType.IsArray || !model.MapType(typeof(IEnumerable)).IsAssignableFrom(listType)) return null; #endif BasicList candidates = new BasicList(); #if WINRT foreach (MethodInfo method in listType.GetRuntimeMethods()) #else foreach (MethodInfo method in listType.GetMethods()) #endif { if (method.IsStatic || method.Name != "Add") continue; ParameterInfo[] parameters = method.GetParameters(); Type paramType; if (parameters.Length == 1 && !candidates.Contains(paramType = parameters[0].ParameterType)) { candidates.Add(paramType); } } string name = listType.Name; bool isQueueStack = name != null && (name.IndexOf("Queue") >= 0 || name.IndexOf("Stack") >= 0); #if !NO_GENERICS if(!isQueueStack) { TestEnumerableListPatterns(model, candidates, listType); #if WINRT foreach (Type iType in listTypeInfo.ImplementedInterfaces) { TestEnumerableListPatterns(model, candidates, iType); } #else foreach (Type iType in listType.GetInterfaces()) { TestEnumerableListPatterns(model, candidates, iType); } #endif } #endif #if WINRT // more convenient GetProperty overload not supported on all platforms foreach (PropertyInfo indexer in listType.GetRuntimeProperties()) { if (indexer.Name != "Item" || candidates.Contains(indexer.PropertyType)) continue; ParameterInfo[] args = indexer.GetIndexParameters(); if (args.Length != 1 || args[0].ParameterType != typeof(int)) continue; MethodInfo getter = indexer.GetMethod; if (getter == null || getter.IsStatic) continue; candidates.Add(indexer.PropertyType); } #else // more convenient GetProperty overload not supported on all platforms foreach (PropertyInfo indexer in listType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { if (indexer.Name != "Item" || candidates.Contains(indexer.PropertyType)) continue; ParameterInfo[] args = indexer.GetIndexParameters(); if (args.Length != 1 || args[0].ParameterType != model.MapType(typeof(int))) continue; candidates.Add(indexer.PropertyType); } #endif switch (candidates.Count) { case 0: return null; case 1: if ((Type)candidates[0] == listType) return null; // recursive return (Type)candidates[0]; case 2: if ((Type)candidates[0] != listType && CheckDictionaryAccessors(model, (Type)candidates[0], (Type)candidates[1])) return (Type)candidates[0]; if ((Type)candidates[1] != listType && CheckDictionaryAccessors(model, (Type)candidates[1], (Type)candidates[0])) return (Type)candidates[1]; break; } return null; } private static void TestEnumerableListPatterns(TypeModel model, BasicList candidates, Type iType) { #if WINRT || COREFX TypeInfo iTypeInfo = iType.GetTypeInfo(); if (iTypeInfo.IsGenericType) { Type typeDef = iTypeInfo.GetGenericTypeDefinition(); if( typeDef == model.MapType(typeof(System.Collections.Generic.IEnumerable<>)) || typeDef == model.MapType(typeof(System.Collections.Generic.ICollection<>)) || typeDef.GetTypeInfo().FullName == "System.Collections.Concurrent.IProducerConsumerCollection`1") { Type[] iTypeArgs = iTypeInfo.GenericTypeArguments; if (!candidates.Contains(iTypeArgs[0])) { candidates.Add(iTypeArgs[0]); } } } #elif !NO_GENERICS if (iType.IsGenericType) { Type typeDef = iType.GetGenericTypeDefinition(); if (typeDef == model.MapType(typeof(System.Collections.Generic.IEnumerable<>)) || typeDef == model.MapType(typeof(System.Collections.Generic.ICollection<>)) || typeDef.FullName == "System.Collections.Concurrent.IProducerConsumerCollection`1") { Type[] iTypeArgs = iType.GetGenericArguments(); if (!candidates.Contains(iTypeArgs[0])) { candidates.Add(iTypeArgs[0]); } } } #endif } private static bool CheckDictionaryAccessors(TypeModel model, Type pair, Type value) { #if NO_GENERICS return false; #elif WINRT || COREFX TypeInfo finalType = pair.GetTypeInfo(); return finalType.IsGenericType && finalType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.KeyValuePair<,>) && finalType.GenericTypeArguments[1] == value; #else return pair.IsGenericType && pair.GetGenericTypeDefinition() == model.MapType(typeof(System.Collections.Generic.KeyValuePair<,>)) && pair.GetGenericArguments()[1] == value; #endif } #if !FEAT_IKVM private bool TryDeserializeList(TypeModel model, ProtoReader reader, DataFormat format, int tag, Type listType, Type itemType, ref object value) { bool isList; MethodInfo addMethod = TypeModel.ResolveListAdd(model, listType, itemType, out isList); if (addMethod == null) throw new NotSupportedException("Unknown list variant: " + listType.FullName); bool found = false; object nextItem = null; IList list = value as IList; object[] args = isList ? null : new object[1]; BasicList arraySurrogate = listType.IsArray ? new BasicList() : null; while (TryDeserializeAuxiliaryType(reader, format, tag, itemType, ref nextItem, true, true, true, true)) { found = true; if (value == null && arraySurrogate == null) { value = CreateListInstance(listType, itemType); list = value as IList; } if (list != null) { list.Add(nextItem); } else if (arraySurrogate != null) { arraySurrogate.Add(nextItem); } else { args[0] = nextItem; addMethod.Invoke(value, args); } nextItem = null; } if (arraySurrogate != null) { Array newArray; if (value != null) { if (arraySurrogate.Count == 0) { // we'll stay with what we had, thanks } else { Array existing = (Array)value; newArray = Array.CreateInstance(itemType, existing.Length + arraySurrogate.Count); Array.Copy(existing, newArray, existing.Length); arraySurrogate.CopyTo(newArray, existing.Length); value = newArray; } } else { newArray = Array.CreateInstance(itemType, arraySurrogate.Count); arraySurrogate.CopyTo(newArray, 0); value = newArray; } } return found; } private static object CreateListInstance(Type listType, Type itemType) { Type concreteListType = listType; if (listType.IsArray) { return Array.CreateInstance(itemType, 0); } #if WINRT || COREFX TypeInfo listTypeInfo = listType.GetTypeInfo(); if (!listTypeInfo.IsClass || listTypeInfo.IsAbstract || Helpers.GetConstructor(listTypeInfo, Helpers.EmptyTypes, true) == null) #else if (!listType.IsClass || listType.IsAbstract || Helpers.GetConstructor(listType, Helpers.EmptyTypes, true) == null) #endif { string fullName; bool handled = false; #if WINRT || COREFX if (listTypeInfo.IsInterface && #else if (listType.IsInterface && #endif (fullName = listType.FullName) != null && fullName.IndexOf("Dictionary") >= 0) // have to try to be frugal here... { #if !NO_GENERICS #if WINRT || COREFX TypeInfo finalType = listType.GetTypeInfo(); if (finalType.IsGenericType && finalType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.IDictionary<,>)) { Type[] genericTypes = listType.GenericTypeArguments; concreteListType = typeof(System.Collections.Generic.Dictionary<,>).MakeGenericType(genericTypes); handled = true; } #else if (listType.IsGenericType && listType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.IDictionary<,>)) { Type[] genericTypes = listType.GetGenericArguments(); concreteListType = typeof(System.Collections.Generic.Dictionary<,>).MakeGenericType(genericTypes); handled = true; } #endif #endif #if !SILVERLIGHT && !WINRT && !PORTABLE && ! COREFX if (!handled && listType == typeof(IDictionary)) { concreteListType = typeof(Hashtable); handled = true; } #endif } #if !NO_GENERICS if (!handled) { concreteListType = typeof(System.Collections.Generic.List<>).MakeGenericType(itemType); handled = true; } #endif #if !SILVERLIGHT && !WINRT && !PORTABLE && ! COREFX if (!handled) { concreteListType = typeof(ArrayList); handled = true; } #endif } return Activator.CreateInstance(concreteListType); } /// /// This is the more "complete" version of Deserialize, which handles single instances of mapped types. /// The value is read as a complete field, including field-header and (for sub-objects) a /// length-prefix..kmc /// /// In addition to that, this provides support for: /// - basic values; individual int / string / Guid / etc /// - IList sets of any type handled by TryDeserializeAuxiliaryType /// internal bool TryDeserializeAuxiliaryType(ProtoReader reader, DataFormat format, int tag, Type type, ref object value, bool skipOtherFields, bool asListItem, bool autoCreate, bool insideList) { if (type == null) throw new ArgumentNullException("type"); Type itemType = null; ProtoTypeCode typecode = Helpers.GetTypeCode(type); int modelKey; WireType wiretype = GetWireType(typecode, format, ref type, out modelKey); bool found = false; if (wiretype == WireType.None) { itemType = GetListItemType(this, type); if (itemType == null && type.IsArray && type.GetArrayRank() == 1 && type != typeof(byte[])) { itemType = type.GetElementType(); } if (itemType != null) { if (insideList) throw TypeModel.CreateNestedListsNotSupported(); found = TryDeserializeList(this, reader, format, tag, type, itemType, ref value); if (!found && autoCreate) { value = CreateListInstance(type, itemType); } return found; } // otherwise, not a happy bunny... ThrowUnexpectedType(type); } // to treat correctly, should read all values while (true) { // for convenience (re complex exit conditions), additional exit test here: // if we've got the value, are only looking for one, and we aren't a list - then exit if (found && asListItem) break; // read the next item int fieldNumber = reader.ReadFieldHeader(); if (fieldNumber <= 0) break; if (fieldNumber != tag) { if (skipOtherFields) { reader.SkipField(); continue; } throw ProtoReader.AddErrorData(new InvalidOperationException( "Expected field " + tag.ToString() + ", but found " + fieldNumber.ToString()), reader); } found = true; reader.Hint(wiretype); // handle signed data etc if (modelKey >= 0) { switch (wiretype) { case WireType.String: case WireType.StartGroup: SubItemToken token = ProtoReader.StartSubItem(reader); value = Deserialize(modelKey, value, reader); ProtoReader.EndSubItem(token, reader); continue; default: value = Deserialize(modelKey, value, reader); continue; } } switch (typecode) { case ProtoTypeCode.Int16: value = reader.ReadInt16(); continue; case ProtoTypeCode.Int32: value = reader.ReadInt32(); continue; case ProtoTypeCode.Int64: value = reader.ReadInt64(); continue; case ProtoTypeCode.UInt16: value = reader.ReadUInt16(); continue; case ProtoTypeCode.UInt32: value = reader.ReadUInt32(); continue; case ProtoTypeCode.UInt64: value = reader.ReadUInt64(); continue; case ProtoTypeCode.Boolean: value = reader.ReadBoolean(); continue; case ProtoTypeCode.SByte: value = reader.ReadSByte(); continue; case ProtoTypeCode.Byte: value = reader.ReadByte(); continue; case ProtoTypeCode.Char: value = (char)reader.ReadUInt16(); continue; case ProtoTypeCode.Double: value = reader.ReadDouble(); continue; case ProtoTypeCode.Single: value = reader.ReadSingle(); continue; case ProtoTypeCode.DateTime: value = BclHelpers.ReadDateTime(reader); continue; case ProtoTypeCode.Decimal: value = BclHelpers.ReadDecimal(reader); continue; case ProtoTypeCode.String: value = reader.ReadString(); continue; case ProtoTypeCode.ByteArray: value = ProtoReader.AppendBytes((byte[])value, reader); continue; case ProtoTypeCode.TimeSpan: value = BclHelpers.ReadTimeSpan(reader); continue; case ProtoTypeCode.Guid: value = BclHelpers.ReadGuid(reader); continue; case ProtoTypeCode.Uri: value = new Uri(reader.ReadString()); continue; } } if (!found && !asListItem && autoCreate) { if (type != typeof(string)) { value = Activator.CreateInstance(type); } } return found; } #endif #if !NO_RUNTIME /// /// Creates a new runtime model, to which the caller /// can add support for a range of types. A model /// can be used "as is", or can be compiled for /// optimal performance. /// public static RuntimeTypeModel Create() { return new RuntimeTypeModel(false); } #endif /// /// Applies common proxy scenarios, resolving the actual type to consider /// protected internal static Type ResolveProxies(Type type) { if (type == null) return null; #if !NO_GENERICS if (type.IsGenericParameter) return null; // Nullable Type tmp = Helpers.GetUnderlyingType(type); if (tmp != null) return tmp; #endif #if !(WINRT || CF) // EF POCO string fullName = type.FullName; if (fullName != null && fullName.StartsWith("System.Data.Entity.DynamicProxies.")) { #if COREFX return type.GetTypeInfo().BaseType; #else return type.BaseType; #endif } // NHibernate Type[] interfaces = type.GetInterfaces(); for(int i = 0 ; i < interfaces.Length ; i++) { switch(interfaces[i].FullName) { case "NHibernate.Proxy.INHibernateProxy": case "NHibernate.Proxy.DynamicProxy.IProxy": case "NHibernate.Intercept.IFieldInterceptorAccessor": #if COREFX return type.GetTypeInfo().BaseType; #else return type.BaseType; #endif } } #endif return null; } /// /// Indicates whether the supplied type is explicitly modelled by the model /// public bool IsDefined(Type type) { return GetKey(ref type) >= 0; } /// /// Provides the key that represents a given type in the current model. /// The type is also normalized for proxies at the same time. /// protected internal int GetKey(ref Type type) { if (type == null) return -1; int key = GetKeyImpl(type); if (key < 0) { Type normalized = ResolveProxies(type); if (normalized != null) { type = normalized; // hence ref key = GetKeyImpl(type); } } return key; } /// /// Provides the key that represents a given type in the current model. /// protected abstract int GetKeyImpl(Type type); /// /// 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 abstract void Serialize(int key, object value, ProtoWriter dest); /// /// 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 abstract object Deserialize(int key, object value, ProtoReader source); //internal ProtoSerializer Create(IProtoSerializer head) //{ // return new RuntimeSerializer(head, this); //} //internal ProtoSerializer Compile /// /// Indicates the type of callback to be used /// protected internal enum CallbackType { /// /// Invoked before an object is serialized /// BeforeSerialize, /// /// Invoked after an object is serialized /// AfterSerialize, /// /// Invoked before an object is deserialized (or when a new instance is created) /// BeforeDeserialize, /// /// Invoked after an object is deserialized /// AfterDeserialize } /// /// Create a deep clone of the supplied instance; any sub-items are also cloned. /// public object DeepClone(object value) { #if FEAT_IKVM throw new NotSupportedException(); #else if (value == null) return null; Type type = value.GetType(); int key = GetKey(ref type); if (key >= 0 && !Helpers.IsEnum(type)) { using (MemoryStream ms = new MemoryStream()) { using(ProtoWriter writer = new ProtoWriter(ms, this, null)) { writer.SetRootObject(value); Serialize(key, value, writer); writer.Close(); } ms.Position = 0; ProtoReader reader = null; try { reader = ProtoReader.Create(ms, this, null, ProtoReader.TO_EOF); return Deserialize(key, null, reader); } finally { ProtoReader.Recycle(reader); } } } int modelKey; if (type == typeof(byte[])) { byte[] orig = (byte[])value, clone = new byte[orig.Length]; Helpers.BlockCopy(orig, 0, clone, 0, orig.Length); return clone; } else if (GetWireType(Helpers.GetTypeCode(type), DataFormat.Default, ref type, out modelKey) != WireType.None && modelKey < 0) { // immutable; just return the original value return value; } using (MemoryStream ms = new MemoryStream()) { using (ProtoWriter writer = new ProtoWriter(ms, this, null)) { if (!TrySerializeAuxiliaryType(writer, type, DataFormat.Default, Serializer.ListItemTag, value, false)) ThrowUnexpectedType(type); writer.Close(); } ms.Position = 0; ProtoReader reader = null; try { reader = ProtoReader.Create(ms, this, null, ProtoReader.TO_EOF); value = null; // start from scratch! TryDeserializeAuxiliaryType(reader, DataFormat.Default, Serializer.ListItemTag, type, ref value, true, false, true, false); return value; } finally { ProtoReader.Recycle(reader); } } #endif } /// /// Indicates that while an inheritance tree exists, the exact type encountered was not /// specified in that hierarchy and cannot be processed. /// protected internal static void ThrowUnexpectedSubtype(Type expected, Type actual) { if (expected != TypeModel.ResolveProxies(actual)) { throw new InvalidOperationException("Unexpected sub-type: " + actual.FullName); } } /// /// Indicates that the given type was not expected, and cannot be processed. /// protected internal static void ThrowUnexpectedType(Type type) { string fullName = type == null ? "(unknown)" : type.FullName; #if !NO_GENERICS && !WINRT if (type != null) { Type baseType = type #if COREFX .GetTypeInfo() #endif .BaseType; if (baseType != null && baseType #if COREFX .GetTypeInfo() #endif .IsGenericType && baseType.GetGenericTypeDefinition().Name == "GeneratedMessage`2") { throw new InvalidOperationException( "Are you mixing protobuf-net and protobuf-csharp-port? See http://stackoverflow.com/q/11564914; type: " + fullName); } } #endif throw new InvalidOperationException("Type is not expected, and no contract can be inferred: " + fullName); } internal static Exception CreateNestedListsNotSupported() { return new NotSupportedException("Nested or jagged lists and arrays are not supported"); } /// /// Indicates that the given type cannot be constructed; it may still be possible to /// deserialize into existing instances. /// public static void ThrowCannotCreateInstance(Type type) { throw new ProtoException("No parameterless constructor found for " + (type == null ? "(null)" : type.Name)); } internal static string SerializeType(TypeModel model, System.Type type) { if (model != null) { TypeFormatEventHandler handler = model.DynamicTypeFormatting; if (handler != null) { TypeFormatEventArgs args = new TypeFormatEventArgs(type); handler(model, args); if (!Helpers.IsNullOrEmpty(args.FormattedName)) return args.FormattedName; } } return type.AssemblyQualifiedName; } internal static System.Type DeserializeType(TypeModel model, string value) { if (model != null) { TypeFormatEventHandler handler = model.DynamicTypeFormatting; if (handler != null) { TypeFormatEventArgs args = new TypeFormatEventArgs(value); handler(model, args); if (args.Type != null) return args.Type; } } return System.Type.GetType(value); } /// /// Returns true if the type supplied is either a recognised contract type, /// or a *list* of a recognised contract type. /// /// Note that primitives always return false, even though the engine /// will, if forced, try to serialize such /// True if this type is recognised as a serializable entity, else false public bool CanSerializeContractType(Type type) { return CanSerialize(type, false, true, true); } /// /// Returns true if the type supplied is a basic type with inbuilt handling, /// a recognised contract type, or a *list* of a basic / contract type. /// public bool CanSerialize(Type type) { return CanSerialize(type, true, true, true); } /// /// Returns true if the type supplied is a basic type with inbuilt handling, /// or a *list* of a basic type with inbuilt handling /// public bool CanSerializeBasicType(Type type) { return CanSerialize(type, true, false, true); } private bool CanSerialize(Type type, bool allowBasic, bool allowContract, bool allowLists) { if (type == null) throw new ArgumentNullException("type"); Type tmp = Helpers.GetUnderlyingType(type); if (tmp != null) type = tmp; // is it a basic type? ProtoTypeCode typeCode = Helpers.GetTypeCode(type); switch(typeCode) { case ProtoTypeCode.Empty: case ProtoTypeCode.Unknown: break; default: return allowBasic; // well-known basic type } int modelKey = GetKey(ref type); if (modelKey >= 0) return allowContract; // known contract type // is it a list? if (allowLists) { Type itemType = null; if (type.IsArray) { // note we don't need to exclude byte[], as that is handled by GetTypeCode already if (type.GetArrayRank() == 1) itemType = type.GetElementType(); } else { itemType = GetListItemType(this, type); } if (itemType != null) return CanSerialize(itemType, allowBasic, allowContract, false); } return false; } /// /// 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 virtual string GetSchema(Type type) { throw new NotSupportedException(); } /// /// Used to provide custom services for writing and parsing type names when using dynamic types. Both parsing and formatting /// are provided on a single API as it is essential that both are mapped identically at all times. /// public event TypeFormatEventHandler DynamicTypeFormatting; #if PLAT_BINARYFORMATTER && !(WINRT || PHONE8 || COREFX) /// /// Creates a new IFormatter that uses protocol-buffer [de]serialization. /// /// A new IFormatter to be used during [de]serialization. /// The type of object to be [de]deserialized by the formatter. public System.Runtime.Serialization.IFormatter CreateFormatter(Type type) { return new Formatter(this, type); } internal sealed class Formatter : System.Runtime.Serialization.IFormatter { private readonly TypeModel model; private readonly Type type; internal Formatter(TypeModel model, Type type) { if (model == null) throw new ArgumentNullException("model"); if (type == null) throw new ArgumentNullException("type"); this.model = model; this.type = type; } private System.Runtime.Serialization.SerializationBinder binder; public System.Runtime.Serialization.SerializationBinder Binder { get { return binder; } set { binder = value; } } private System.Runtime.Serialization.StreamingContext context; public System.Runtime.Serialization.StreamingContext Context { get { return context; } set { context = value; } } public object Deserialize(Stream source) { #if FEAT_IKVM throw new NotSupportedException(); #else return model.Deserialize(source, null, type, -1, Context); #endif } public void Serialize(Stream destination, object graph) { model.Serialize(destination, graph, Context); } private System.Runtime.Serialization.ISurrogateSelector surrogateSelector; public System.Runtime.Serialization.ISurrogateSelector SurrogateSelector { get { return surrogateSelector; } set { surrogateSelector = value; } } } #endif #if DEBUG // this is used by some unit tests only, to ensure no buffering when buffering is disabled private bool forwardsOnly; /// /// If true, buffering of nested objects is disabled /// public bool ForwardsOnly { get { return forwardsOnly; } set { forwardsOnly = value; } } #endif internal virtual Type GetType(string fullName, Assembly context) { #if FEAT_IKVM throw new NotImplementedException(); #else return ResolveKnownType(fullName, this, context); #endif } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] internal static Type ResolveKnownType(string name, TypeModel model, Assembly assembly) { if (Helpers.IsNullOrEmpty(name)) return null; try { #if FEAT_IKVM // looks like a NullReferenceException, but this should call into RuntimeTypeModel's version Type type = model == null ? null : model.GetType(name, assembly); #else Type type = Type.GetType(name); #endif if (type != null) return type; } catch { } try { int i = name.IndexOf(','); string fullName = (i > 0 ? name.Substring(0, i) : name).Trim(); #if !(WINRT || FEAT_IKVM || COREFX) if (assembly == null) assembly = Assembly.GetCallingAssembly(); #endif Type type = assembly == null ? null : assembly.GetType(fullName); if (type != null) return type; } catch { } return null; } } }