2 // KnownTypeCollection.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2005 Novell, Inc. http://www.novell.com
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using System.Collections;
31 using System.Collections.Generic;
32 using System.Collections.ObjectModel;
34 using System.Reflection;
36 using System.Xml.Schema;
38 using QName = System.Xml.XmlQualifiedName;
39 using System.Xml.Serialization;
41 namespace System.Runtime.Serialization
44 XmlFormatter implementation design inference:
47 - No XML Schema types are directly used. There are some maps from
48 xs:blahType to ms:blahType where the namespaceURI for prefix "ms" is
49 "http://schemas.microsoft.com/2003/10/Serialization/" .
52 - An object being serialized 1) must be of type System.Object, or
53 2) must be null, or 3) must have either a [DataContract] attribute
54 or a [Serializable] attribute to be serializable.
55 - When the object is either of type System.Object or null, then the
56 XML type is "anyType".
57 - When the object is [Serializable], then the runtime-serialization
58 compatible object graph is written.
59 - Otherwise the serialization is based on contract attributes.
60 ([Serializable] takes precedence).
63 - For type A to be serializable, the base type B of A must be
65 - If a type which is [Serializable] and whose base type has a
66 [DataContract], then for base type members [DataContract] is taken.
67 - It is vice versa i.e. if the base type is [Serializable] and the
68 derived type has a [DataContract], then [Serializable] takes place
71 known type collection:
72 - It internally manages mapping store keyed by contract QNames.
73 KnownTypeCollection.Add() checks if the same QName contract already
74 exists (and raises InvalidOperationException if required).
77 internal static class TypeExtensions
79 public static T GetCustomAttribute<T> (this MemberInfo type, bool inherit)
81 var arr = type.GetCustomAttributes (typeof (T), inherit);
82 return arr != null && arr.Length == 1 ? (T) arr [0] : default (T);
85 public static IEnumerable<Type> GetInterfacesOrSelfInterface (this Type type)
89 foreach (var t in type.GetInterfaces ())
94 internal sealed class KnownTypeCollection : Collection<Type>
96 internal const string MSSimpleNamespace =
97 "http://schemas.microsoft.com/2003/10/Serialization/";
98 internal const string MSArraysNamespace =
99 "http://schemas.microsoft.com/2003/10/Serialization/Arrays";
100 internal const string DefaultClrNamespaceBase =
101 "http://schemas.datacontract.org/2004/07/";
102 internal const string DefaultClrNamespaceSystem =
103 "http://schemas.datacontract.org/2004/07/System";
106 static QName any_type, bool_type,
107 byte_type, date_type, decimal_type, double_type,
108 float_type, string_type,
109 short_type, int_type, long_type,
110 ubyte_type, ushort_type, uint_type, ulong_type,
112 any_uri_type, base64_type, duration_type, qname_type,
113 // custom in ms nsURI schema
114 char_type, guid_type,
115 // not in ms nsURI schema
116 dbnull_type, date_time_offset_type;
118 static KnownTypeCollection ()
120 string s = MSSimpleNamespace;
121 any_type = new QName ("anyType", s);
122 any_uri_type = new QName ("anyURI", s);
123 bool_type = new QName ("boolean", s);
124 base64_type = new QName ("base64Binary", s);
125 date_type = new QName ("dateTime", s);
126 duration_type = new QName ("duration", s);
127 qname_type = new QName ("QName", s);
128 decimal_type = new QName ("decimal", s);
129 double_type = new QName ("double", s);
130 float_type = new QName ("float", s);
131 byte_type = new QName ("byte", s);
132 short_type = new QName ("short", s);
133 int_type = new QName ("int", s);
134 long_type = new QName ("long", s);
135 ubyte_type = new QName ("unsignedByte", s);
136 ushort_type = new QName ("unsignedShort", s);
137 uint_type = new QName ("unsignedInt", s);
138 ulong_type = new QName ("unsignedLong", s);
139 string_type = new QName ("string", s);
140 guid_type = new QName ("guid", s);
141 char_type = new QName ("char", s);
143 dbnull_type = new QName ("DBNull", DefaultClrNamespaceBase + "System");
144 date_time_offset_type = new QName ("DateTimeOffset", DefaultClrNamespaceBase + "System");
147 // FIXME: find out how QName and guid are processed
149 internal QName GetXmlName (Type type)
151 SerializationMap map = FindUserMap (type);
154 return GetPredefinedTypeName (type);
157 internal static QName GetPredefinedTypeName (Type type)
159 QName name = GetPrimitiveTypeName (type);
160 if (name != QName.Empty)
162 if (type == typeof (DBNull))
164 if (type == typeof (DateTimeOffset))
165 return date_time_offset_type;
169 internal static QName GetPrimitiveTypeName (Type type)
171 if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>))
172 return GetPrimitiveTypeName (type.GetGenericArguments () [0]);
177 switch (Type.GetTypeCode (type)) {
178 case TypeCode.Object: // other than System.Object
179 case TypeCode.DBNull: // it is natively mapped, but not in ms serialization namespace.
182 if (type == typeof (object))
184 if (type == typeof (Guid))
186 if (type == typeof (TimeSpan))
187 return duration_type;
188 if (type == typeof (byte []))
190 if (type == typeof (Uri))
193 case TypeCode.Boolean:
199 case TypeCode.DateTime:
201 case TypeCode.Decimal:
203 case TypeCode.Double:
213 case TypeCode.Single:
215 case TypeCode.String:
217 case TypeCode.UInt16:
219 case TypeCode.UInt32:
221 case TypeCode.UInt64:
226 internal static string PredefinedTypeObjectToString (object obj)
228 Type type = obj.GetType ();
229 switch (Type.GetTypeCode (type)) {
230 case TypeCode.Object: // other than System.Object
233 if (type == typeof (object))
235 if (type == typeof (Guid))
236 return XmlConvert.ToString ((Guid) obj);
237 if (type == typeof (TimeSpan))
238 return XmlConvert.ToString ((TimeSpan) obj);
239 if (type == typeof (byte []))
240 return Convert.ToBase64String ((byte []) obj);
241 if (type == typeof (Uri))
242 return ((Uri) obj).ToString ();
243 throw new Exception ("Internal error: missing predefined type serialization for type " + type.FullName);
244 case TypeCode.DBNull: // predefined, but not primitive
246 case TypeCode.Boolean:
247 return XmlConvert.ToString ((bool) obj);
249 return XmlConvert.ToString ((int)((byte) obj));
251 return XmlConvert.ToString ((uint) (char) obj);
252 case TypeCode.DateTime:
253 return XmlConvert.ToString ((DateTime) obj, XmlDateTimeSerializationMode.RoundtripKind);
254 case TypeCode.Decimal:
255 return XmlConvert.ToString ((decimal) obj);
256 case TypeCode.Double:
257 return XmlConvert.ToString ((double) obj);
259 return XmlConvert.ToString ((short) obj);
261 return XmlConvert.ToString ((int) obj);
263 return XmlConvert.ToString ((long) obj);
265 return XmlConvert.ToString ((sbyte) obj);
266 case TypeCode.Single:
267 return XmlConvert.ToString ((float) obj);
268 case TypeCode.String:
270 case TypeCode.UInt16:
271 return XmlConvert.ToString ((int) (ushort) obj);
272 case TypeCode.UInt32:
273 return XmlConvert.ToString ((uint) obj);
274 case TypeCode.UInt64:
275 return XmlConvert.ToString ((ulong) obj);
279 internal static Type GetPrimitiveTypeFromName (QName name)
281 switch (name.Namespace) {
282 case DefaultClrNamespaceSystem:
285 return typeof (DBNull);
286 case "DateTimeOffset":
287 return typeof (DateTimeOffset);
290 // FIXME: xsd types and ms serialization types should be differentiated. (usage problem)
291 case XmlSchema.Namespace:
292 case MSSimpleNamespace:
297 return typeof (bool);
299 return typeof (byte []);
301 return typeof (DateTime);
303 return typeof (TimeSpan);
305 return typeof (QName);
307 return typeof (decimal);
309 return typeof (double);
311 return typeof (float);
313 return typeof (sbyte);
315 return typeof (short);
319 return typeof (long);
321 return typeof (byte);
322 case "unsignedShort":
323 return typeof (ushort);
325 return typeof (uint);
327 return typeof (ulong);
329 return typeof (string);
331 return typeof (object);
333 return typeof (Guid);
335 return typeof (char);
343 internal static object PredefinedTypeStringToObject (string s,
344 string name, XmlReader reader)
348 return new Uri(s,UriKind.RelativeOrAbsolute);
350 return XmlConvert.ToBoolean (s);
352 return Convert.FromBase64String (s);
354 return XmlConvert.ToDateTime (s, XmlDateTimeSerializationMode.RoundtripKind);
356 return XmlConvert.ToTimeSpan (s);
358 int idx = s.IndexOf (':');
359 string l = idx < 0 ? s : s.Substring (idx + 1);
360 return idx < 0 ? new QName (l) :
361 new QName (l, reader.LookupNamespace (
362 s.Substring (0, idx)));
364 return XmlConvert.ToDecimal (s);
366 return XmlConvert.ToDouble (s);
368 return XmlConvert.ToSingle (s);
370 return XmlConvert.ToSByte (s);
372 return XmlConvert.ToInt16 (s);
374 return XmlConvert.ToInt32 (s);
376 return XmlConvert.ToInt64 (s);
378 return XmlConvert.ToByte (s);
379 case "unsignedShort":
380 return XmlConvert.ToUInt16 (s);
382 return XmlConvert.ToUInt32 (s);
384 return XmlConvert.ToUInt64 (s);
388 return XmlConvert.ToGuid (s);
392 return (char) XmlConvert.ToUInt32 (s);
394 throw new Exception ("Unanticipated primitive type: " + name);
398 List<SerializationMap> contracts = new List<SerializationMap> ();
400 public KnownTypeCollection ()
404 protected override void ClearItems ()
409 protected override void InsertItem (int index, Type type)
411 if (ShouldNotRegister (type))
413 if (!Contains (type)) {
415 base.InsertItem (index, type);
419 // FIXME: it could remove other types' dependencies.
420 protected override void RemoveItem (int index)
423 DoRemoveItem (index);
426 void DoRemoveItem (int index)
428 Type t = base [index];
429 List<SerializationMap> l = new List<SerializationMap> ();
430 foreach (SerializationMap m in contracts) {
431 if (m.RuntimeType == t)
434 foreach (SerializationMap m in l) {
435 contracts.Remove (m);
436 base.RemoveItem (index);
440 protected override void SetItem (int index, Type type)
442 if (ShouldNotRegister (type))
445 // Since this collection is not assured to be ordered, it ignores the whole Set operation if the type already exists.
451 if (TryRegister (type))
452 base.InsertItem (index - 1, type);
455 internal SerializationMap FindUserMap (QName qname)
458 return contracts.FirstOrDefault (c => c.XmlName == qname);
461 internal Type GetSerializedType (Type type)
463 if (IsPrimitiveNotEnum (type))
465 Type element = GetCollectionElementType (type);
468 QName name = GetQName (type);
469 var map = FindUserMap (name);
471 return map.RuntimeType;
475 internal SerializationMap FindUserMap (Type type)
478 for (int i = 0; i < contracts.Count; i++)
479 if (type == contracts [i].RuntimeType)
480 return contracts [i];
485 internal QName GetQName (Type type)
487 SerializationMap map = FindUserMap (type);
491 return GetStaticQName (type);
494 public static QName GetStaticQName (Type type)
496 if (IsPrimitiveNotEnum (type))
497 return GetPrimitiveTypeName (type);
500 return GetEnumQName (type);
502 QName qname = GetContractQName (type);
506 if (type.GetInterface ("System.Xml.Serialization.IXmlSerializable") != null)
507 //FIXME: Reusing GetSerializableQName here, since we just
508 //need name of the type..
509 return GetSerializableQName (type);
511 qname = GetCollectionContractQName (type);
515 Type element = GetCollectionElementType (type);
517 return GetCollectionQName (element);
519 if (GetAttribute<SerializableAttribute> (type) != null)
520 return GetSerializableQName (type);
522 // default type map - still uses GetContractQName().
523 return GetContractQName (type, null, null);
526 internal static QName GetContractQName (Type type)
528 var a = GetAttribute<DataContractAttribute> (type);
529 return a == null ? null : GetContractQName (type, a.Name, a.Namespace);
532 static QName GetCollectionContractQName (Type type)
534 var a = GetAttribute<CollectionDataContractAttribute> (type);
535 return a == null ? null : GetContractQName (type, a.Name, a.Namespace);
538 static QName GetContractQName (Type type, string name, string ns)
541 name = GetDefaultName (type);
543 ns = GetDefaultNamespace (type);
544 return new QName (name, ns);
547 static QName GetEnumQName (Type type)
549 string name = null, ns = null;
554 var dca = GetAttribute<DataContractAttribute> (type);
562 ns = GetDefaultNamespace (type);
565 name = type.Namespace == null ? type.Name : type.FullName.Substring (type.Namespace.Length + 1).Replace ('+', '.');
567 return new QName (name, ns);
570 internal static string GetDefaultName (Type type)
572 // FIXME: there could be decent ways to get
573 // the same result...
574 string name = type.Namespace == null || type.Namespace.Length == 0 ? type.Name : type.FullName.Substring (type.Namespace.Length + 1).Replace ('+', '.');
575 if (type.IsGenericType) {
576 name = name.Substring (0, name.IndexOf ('`')) + "Of";
577 foreach (var t in type.GetGenericArguments ())
578 name += t.Name; // FIXME: check namespaces too
583 internal static string GetDefaultNamespace (Type type)
585 foreach (ContractNamespaceAttribute a in type.Assembly.GetCustomAttributes (typeof (ContractNamespaceAttribute), true))
586 if (a.ClrNamespace == type.Namespace)
587 return a.ContractNamespace;
588 return DefaultClrNamespaceBase + type.Namespace;
591 static QName GetCollectionQName (Type element)
593 QName eqname = GetStaticQName (element);
595 string ns = eqname.Namespace;
596 if (eqname.Namespace == MSSimpleNamespace)
597 //Arrays of Primitive types
598 ns = MSArraysNamespace;
601 "ArrayOf" + XmlConvert.EncodeLocalName (eqname.Name),
605 static QName GetSerializableQName (Type type)
608 // First, check XmlSchemaProviderAttribute and try GetSchema() to see if it returns a schema in the expected format.
609 var xpa = type.GetCustomAttribute<XmlSchemaProviderAttribute> (true);
611 var mi = type.GetMethod (xpa.MethodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
614 var xss = new XmlSchemaSet ();
615 return (XmlQualifiedName) mi.Invoke (null, new object [] {xss});
623 string xmlName = type.Name;
624 if (type.IsGenericType) {
625 xmlName = xmlName.Substring (0, xmlName.IndexOf ('`')) + "Of";
626 foreach (var t in type.GetGenericArguments ())
627 xmlName += GetStaticQName (t).Name; // FIXME: check namespaces too
629 string xmlNamespace = GetDefaultNamespace (type);
630 var x = GetAttribute<XmlRootAttribute> (type);
632 xmlName = x.ElementName;
633 xmlNamespace = x.Namespace;
635 return new QName (XmlConvert.EncodeLocalName (xmlName), xmlNamespace);
638 static bool IsPrimitiveNotEnum (Type type)
642 if (Type.GetTypeCode (type) != TypeCode.Object) // explicitly primitive
644 if (type == typeof (Guid) || type == typeof (object) || type == typeof(TimeSpan) || type == typeof(byte[]) || type==typeof(Uri)) // special primitives
647 if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>))
648 return IsPrimitiveNotEnum (type.GetGenericArguments () [0]);
652 bool ShouldNotRegister (Type type)
654 return IsPrimitiveNotEnum (type);
657 internal bool TryRegister (Type type)
660 return DoTryRegister (type);
664 bool DoTryRegister (Type type)
666 // exclude predefined maps
667 if (ShouldNotRegister (type))
670 if (FindUserMap (type) != null)
673 if (RegisterEnum (type) != null)
676 if (RegisterDictionary (type) != null)
679 if (RegisterCollectionContract (type) != null)
682 if (RegisterContract (type) != null)
685 if (RegisterIXmlSerializable (type) != null)
688 if (RegisterCollection (type) != null)
691 if (GetAttribute<SerializableAttribute> (type) != null) {
692 RegisterSerializable (type);
696 RegisterDefaultTypeMap (type);
700 static Type GetCollectionElementType (Type type)
703 return type.GetElementType ();
704 var ifaces = type.GetInterfacesOrSelfInterface ();
705 foreach (Type i in ifaces)
706 if (i.IsGenericType && i.GetGenericTypeDefinition ().Equals (typeof (IEnumerable<>)))
707 return i.GetGenericArguments () [0];
708 foreach (Type i in ifaces)
709 if (i == typeof (IList))
710 return typeof (object);
714 internal static T GetAttribute<T> (ICustomAttributeProvider ap) where T : Attribute
716 object [] atts = ap.GetCustomAttributes (typeof (T), false);
717 return atts.Length == 0 ? null : (T) atts [0];
720 private CollectionContractTypeMap RegisterCollectionContract (Type type)
722 var cdca = GetAttribute<CollectionDataContractAttribute> (type);
726 Type element = GetCollectionElementType (type);
728 throw new InvalidOperationException (String.Format ("Type '{0}' is marked as collection contract, but it is not a collection", type));
730 TryRegister (element); // must be registered before the name conflict check.
732 QName qname = GetCollectionContractQName (type);
733 CheckStandardQName (qname);
734 var map = FindUserMap (qname);
736 var cmap = map as CollectionContractTypeMap;
737 if (cmap == null) // The runtime type may still differ (between array and other IList; see bug #670560)
738 throw new InvalidOperationException (String.Format ("Failed to add type {0} to known type collection. There already is a registered type for XML name {1}", type, qname));
741 var ret = new CollectionContractTypeMap (type, cdca, element, qname, this);
746 private CollectionTypeMap RegisterCollection (Type type)
748 Type element = GetCollectionElementType (type);
752 TryRegister (element);
754 QName qname = GetCollectionQName (element);
756 var map = FindUserMap (qname);
758 var cmap = map as CollectionTypeMap;
759 if (cmap == null) // The runtime type may still differ (between array and other IList; see bug #670560)
760 throw new InvalidOperationException (String.Format ("Failed to add type {0} to known type collection. There already is a registered type for XML name {1}", type, qname));
764 CollectionTypeMap ret =
765 new CollectionTypeMap (type, element, qname, this);
770 static bool TypeImplementsIDictionary (Type type)
772 foreach (var iface in type.GetInterfacesOrSelfInterface ())
773 if (iface == typeof (IDictionary) || (iface.IsGenericType && iface.GetGenericTypeDefinition () == typeof (IDictionary<,>)))
779 // it also supports contract-based dictionary.
780 private DictionaryTypeMap RegisterDictionary (Type type)
782 if (!TypeImplementsIDictionary (type))
785 var cdca = GetAttribute<CollectionDataContractAttribute> (type);
787 DictionaryTypeMap ret =
788 new DictionaryTypeMap (type, cdca, this);
790 TryRegister (ret.KeyType);
791 TryRegister (ret.ValueType);
793 var map = FindUserMap (ret.XmlName);
795 var dmap = map as DictionaryTypeMap;
796 if (dmap == null) // The runtime type may still differ (between array and other IList; see bug #670560)
797 throw new InvalidOperationException (String.Format ("Failed to add type {0} to known type collection. There already is a registered type for XML name {1}", type, ret.XmlName));
804 private SerializationMap RegisterSerializable (Type type)
806 QName qname = GetSerializableQName (type);
808 if (FindUserMap (qname) != null)
809 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
811 SharedTypeMap ret = new SharedTypeMap (type, qname, this);
817 private SerializationMap RegisterIXmlSerializable (Type type)
819 if (type.GetInterface ("System.Xml.Serialization.IXmlSerializable") == null)
822 QName qname = GetSerializableQName (type);
824 if (FindUserMap (qname) != null)
825 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
827 XmlSerializableMap ret = new XmlSerializableMap (type, qname, this);
833 void CheckStandardQName (QName qname)
835 switch (qname.Namespace) {
836 case XmlSchema.Namespace:
837 case XmlSchema.InstanceNamespace:
838 case MSSimpleNamespace:
839 case MSArraysNamespace:
840 throw new InvalidOperationException (String.Format ("Namespace {0} is reserved and cannot be used for user serialization", qname.Namespace));
845 private SharedContractMap RegisterContract (Type type)
847 QName qname = GetContractQName (type);
850 CheckStandardQName (qname);
851 if (FindUserMap (qname) != null)
852 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
854 SharedContractMap ret = new SharedContractMap (type, qname, this);
858 if (type.BaseType != typeof (object)) {
859 TryRegister (type.BaseType);
860 if (!FindUserMap (type.BaseType).IsContractAllowedType)
861 throw new InvalidDataContractException (String.Format ("To be serializable by data contract, type '{0}' cannot inherit from non-contract and non-Serializable type '{1}'", type, type.BaseType));
864 object [] attrs = type.GetCustomAttributes (typeof (KnownTypeAttribute), true);
865 for (int i = 0; i < attrs.Length; i++) {
866 KnownTypeAttribute kt = (KnownTypeAttribute) attrs [i];
867 TryRegister (kt.Type);
873 DefaultTypeMap RegisterDefaultTypeMap (Type type)
875 DefaultTypeMap ret = new DefaultTypeMap (type, this);
881 private EnumMap RegisterEnum (Type type)
883 QName qname = GetEnumQName (type);
887 if (FindUserMap (qname) != null)
888 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
891 new EnumMap (type, qname, this);