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);
86 internal sealed class KnownTypeCollection : Collection<Type>
88 internal const string MSSimpleNamespace =
89 "http://schemas.microsoft.com/2003/10/Serialization/";
90 internal const string MSArraysNamespace =
91 "http://schemas.microsoft.com/2003/10/Serialization/Arrays";
92 internal const string DefaultClrNamespaceBase =
93 "http://schemas.datacontract.org/2004/07/";
95 static QName any_type, bool_type,
96 byte_type, date_type, decimal_type, double_type,
97 float_type, string_type,
98 short_type, int_type, long_type,
99 ubyte_type, ushort_type, uint_type, ulong_type,
101 any_uri_type, base64_type, duration_type, qname_type,
102 // custom in ms nsURI schema
103 char_type, guid_type,
104 // not in ms nsURI schema
107 static KnownTypeCollection ()
109 //any_type, bool_type, byte_type, date_type, decimal_type, double_type, float_type, string_type,
110 // short_type, int_type, long_type, ubyte_type, ushort_type, uint_type, ulong_type,
111 // any_uri_type, base64_type, duration_type, qname_type,
112 // char_type, guid_type, dbnull_type;
113 string s = MSSimpleNamespace;
114 any_type = new QName ("anyType", s);
115 any_uri_type = new QName ("anyURI", s);
116 bool_type = new QName ("boolean", s);
117 base64_type = new QName ("base64Binary", s);
118 date_type = new QName ("dateTime", s);
119 duration_type = new QName ("duration", s);
120 qname_type = new QName ("QName", s);
121 decimal_type = new QName ("decimal", s);
122 double_type = new QName ("double", s);
123 float_type = new QName ("float", s);
124 byte_type = new QName ("byte", s);
125 short_type = new QName ("short", s);
126 int_type = new QName ("int", s);
127 long_type = new QName ("long", s);
128 ubyte_type = new QName ("unsignedByte", s);
129 ushort_type = new QName ("unsignedShort", s);
130 uint_type = new QName ("unsignedInt", s);
131 ulong_type = new QName ("unsignedLong", s);
132 string_type = new QName ("string", s);
133 guid_type = new QName ("guid", s);
134 char_type = new QName ("char", s);
136 dbnull_type = new QName ("DBNull", MSSimpleNamespace + "System");
139 // FIXME: find out how QName and guid are processed
141 internal QName GetXmlName (Type type)
143 SerializationMap map = FindUserMap (type);
146 return GetPredefinedTypeName (type);
149 internal static QName GetPredefinedTypeName (Type type)
151 QName name = GetPrimitiveTypeName (type);
152 if (name != QName.Empty)
154 if (type == typeof (DBNull))
159 internal static QName GetPrimitiveTypeName (Type type)
161 if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>))
162 return GetPrimitiveTypeName (type.GetGenericArguments () [0]);
167 switch (Type.GetTypeCode (type)) {
168 case TypeCode.Object: // other than System.Object
169 case TypeCode.DBNull: // it is natively mapped, but not in ms serialization namespace.
172 if (type == typeof (object))
174 if (type == typeof (Guid))
176 if (type == typeof (TimeSpan))
177 return duration_type;
178 if (type == typeof (byte []))
180 if (type == typeof (Uri))
183 case TypeCode.Boolean:
189 case TypeCode.DateTime:
191 case TypeCode.Decimal:
193 case TypeCode.Double:
203 case TypeCode.Single:
205 case TypeCode.String:
207 case TypeCode.UInt16:
209 case TypeCode.UInt32:
211 case TypeCode.UInt64:
216 internal static string PredefinedTypeObjectToString (object obj)
218 Type type = obj.GetType ();
219 switch (Type.GetTypeCode (type)) {
220 case TypeCode.Object: // other than System.Object
223 if (type == typeof (object))
225 if (type == typeof (Guid))
226 return XmlConvert.ToString ((Guid) obj);
227 if (type == typeof (TimeSpan))
228 return XmlConvert.ToString ((TimeSpan) obj);
229 if (type == typeof (byte []))
230 return Convert.ToBase64String ((byte []) obj);
231 if (type == typeof (Uri))
232 return ((Uri) obj).ToString ();
233 throw new Exception ("Internal error: missing predefined type serialization for type " + type.FullName);
234 case TypeCode.DBNull: // predefined, but not primitive
236 case TypeCode.Boolean:
237 return XmlConvert.ToString ((bool) obj);
239 return XmlConvert.ToString ((int)((byte) obj));
241 return XmlConvert.ToString ((uint) (char) obj);
242 case TypeCode.DateTime:
243 return XmlConvert.ToString ((DateTime) obj, XmlDateTimeSerializationMode.RoundtripKind);
244 case TypeCode.Decimal:
245 return XmlConvert.ToString ((decimal) obj);
246 case TypeCode.Double:
247 return XmlConvert.ToString ((double) obj);
249 return XmlConvert.ToString ((short) obj);
251 return XmlConvert.ToString ((int) obj);
253 return XmlConvert.ToString ((long) obj);
255 return XmlConvert.ToString ((sbyte) obj);
256 case TypeCode.Single:
257 return XmlConvert.ToString ((float) obj);
258 case TypeCode.String:
260 case TypeCode.UInt16:
261 return XmlConvert.ToString ((int) (ushort) obj);
262 case TypeCode.UInt32:
263 return XmlConvert.ToString ((uint) obj);
264 case TypeCode.UInt64:
265 return XmlConvert.ToString ((ulong) obj);
269 // FIXME: xsd types and ms serialization types should be differentiated.
270 internal static Type GetPrimitiveTypeFromName (string name)
276 return typeof (bool);
278 return typeof (byte []);
280 return typeof (DateTime);
282 return typeof (TimeSpan);
284 return typeof (QName);
286 return typeof (decimal);
288 return typeof (double);
290 return typeof (float);
292 return typeof (sbyte);
294 return typeof (short);
298 return typeof (long);
300 return typeof (byte);
301 case "unsignedShort":
302 return typeof (ushort);
304 return typeof (uint);
306 return typeof (ulong);
308 return typeof (string);
310 return typeof (object);
312 return typeof (Guid);
314 return typeof (char);
321 internal static object PredefinedTypeStringToObject (string s,
322 string name, XmlReader reader)
326 return new Uri(s,UriKind.RelativeOrAbsolute);
328 return XmlConvert.ToBoolean (s);
330 return Convert.FromBase64String (s);
332 return XmlConvert.ToDateTime (s, XmlDateTimeSerializationMode.RoundtripKind);
334 return XmlConvert.ToTimeSpan (s);
336 int idx = s.IndexOf (':');
337 string l = idx < 0 ? s : s.Substring (idx + 1);
338 return idx < 0 ? new QName (l) :
339 new QName (l, reader.LookupNamespace (
340 s.Substring (0, idx)));
342 return XmlConvert.ToDecimal (s);
344 return XmlConvert.ToDouble (s);
346 return XmlConvert.ToSingle (s);
348 return XmlConvert.ToSByte (s);
350 return XmlConvert.ToInt16 (s);
352 return XmlConvert.ToInt32 (s);
354 return XmlConvert.ToInt64 (s);
356 return XmlConvert.ToByte (s);
357 case "unsignedShort":
358 return XmlConvert.ToUInt16 (s);
360 return XmlConvert.ToUInt32 (s);
362 return XmlConvert.ToUInt64 (s);
366 return XmlConvert.ToGuid (s);
370 return (char) XmlConvert.ToUInt32 (s);
372 throw new Exception ("Unanticipated primitive type: " + name);
376 List<SerializationMap> contracts = new List<SerializationMap> ();
378 public KnownTypeCollection ()
382 protected override void ClearItems ()
387 protected override void InsertItem (int index, Type type)
389 if (ShouldNotRegister (type))
391 if (!Contains (type)) {
393 base.InsertItem (index, type);
397 // FIXME: it could remove other types' dependencies.
398 protected override void RemoveItem (int index)
400 Type t = base [index];
401 List<SerializationMap> l = new List<SerializationMap> ();
402 foreach (SerializationMap m in contracts) {
403 if (m.RuntimeType == t)
406 foreach (SerializationMap m in l) {
407 contracts.Remove (m);
408 base.RemoveItem (index);
412 protected override void SetItem (int index, Type type)
414 if (ShouldNotRegister (type))
417 // Since this collection is not assured to be ordered, it ignores the whole Set operation if the type already exists.
423 if (TryRegister (type))
424 base.InsertItem (index - 1, type);
427 internal SerializationMap FindUserMap (QName qname)
429 return contracts.FirstOrDefault (c => c.XmlName == qname);
432 internal Type GetSerializedType (Type type)
434 Type element = GetCollectionElementType (type);
437 QName name = GetQName (type);
438 var map = FindUserMap (name);
440 return map.RuntimeType;
444 internal SerializationMap FindUserMap (Type type)
446 for (int i = 0; i < contracts.Count; i++)
447 if (type == contracts [i].RuntimeType)
448 return contracts [i];
452 internal QName GetQName (Type type)
454 SerializationMap map = FindUserMap (type);
458 return GetStaticQName (type);
461 public static QName GetStaticQName (Type type)
463 if (IsPrimitiveNotEnum (type))
464 return GetPrimitiveTypeName (type);
467 return GetEnumQName (type);
469 QName qname = GetContractQName (type);
473 if (type.GetInterface ("System.Xml.Serialization.IXmlSerializable") != null)
474 //FIXME: Reusing GetSerializableQName here, since we just
475 //need name of the type..
476 return GetSerializableQName (type);
478 qname = GetCollectionContractQName (type);
482 Type element = GetCollectionElementType (type);
484 return GetCollectionQName (element);
486 if (GetAttribute<SerializableAttribute> (type) != null)
487 return GetSerializableQName (type);
489 // default type map - still uses GetContractQName().
490 return GetContractQName (type, null, null);
493 internal static QName GetContractQName (Type type)
495 var a = GetAttribute<DataContractAttribute> (type);
496 return a == null ? null : GetContractQName (type, a.Name, a.Namespace);
499 static QName GetCollectionContractQName (Type type)
501 var a = GetAttribute<CollectionDataContractAttribute> (type);
502 return a == null ? null : GetContractQName (type, a.Name, a.Namespace);
505 static QName GetContractQName (Type type, string name, string ns)
508 name = GetDefaultName (type);
510 ns = GetDefaultNamespace (type);
511 return new QName (name, ns);
514 static QName GetEnumQName (Type type)
516 string name = null, ns = null;
521 var dca = GetAttribute<DataContractAttribute> (type);
529 ns = GetDefaultNamespace (type);
532 name = type.Namespace == null ? type.Name : type.FullName.Substring (type.Namespace.Length + 1).Replace ('+', '.');
534 return new QName (name, ns);
537 internal static string GetDefaultName (Type type)
539 // FIXME: there could be decent ways to get
540 // the same result...
541 string name = type.Namespace == null || type.Namespace.Length == 0 ? type.Name : type.FullName.Substring (type.Namespace.Length + 1).Replace ('+', '.');
542 if (type.IsGenericType) {
543 name = name.Substring (0, name.IndexOf ('`')) + "Of";
544 foreach (var t in type.GetGenericArguments ())
545 name += t.Name; // FIXME: check namespaces too
550 internal static string GetDefaultNamespace (Type type)
552 foreach (ContractNamespaceAttribute a in type.Assembly.GetCustomAttributes (typeof (ContractNamespaceAttribute), true))
553 if (a.ClrNamespace == type.Namespace)
554 return a.ContractNamespace;
555 return DefaultClrNamespaceBase + type.Namespace;
558 static QName GetCollectionQName (Type element)
560 QName eqname = GetStaticQName (element);
562 string ns = eqname.Namespace;
563 if (eqname.Namespace == MSSimpleNamespace)
564 //Arrays of Primitive types
565 ns = MSArraysNamespace;
568 "ArrayOf" + XmlConvert.EncodeLocalName (eqname.Name),
572 static QName GetSerializableQName (Type type)
575 // First, check XmlSchemaProviderAttribute and try GetSchema() to see if it returns a schema in the expected format.
576 var xpa = type.GetCustomAttribute<XmlSchemaProviderAttribute> (true);
578 var mi = type.GetMethod (xpa.MethodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
581 var xss = new XmlSchemaSet ();
582 return (XmlQualifiedName) mi.Invoke (null, new object [] {xss});
590 string xmlName = type.Name;
591 if (type.IsGenericType) {
592 xmlName = xmlName.Substring (0, xmlName.IndexOf ('`')) + "Of";
593 foreach (var t in type.GetGenericArguments ())
594 xmlName += GetStaticQName (t).Name; // FIXME: check namespaces too
596 string xmlNamespace = GetDefaultNamespace (type);
597 var x = GetAttribute<XmlRootAttribute> (type);
599 xmlName = x.ElementName;
600 xmlNamespace = x.Namespace;
602 return new QName (XmlConvert.EncodeLocalName (xmlName), xmlNamespace);
605 static bool IsPrimitiveNotEnum (Type type)
609 if (Type.GetTypeCode (type) != TypeCode.Object) // explicitly primitive
611 if (type == typeof (Guid) || type == typeof (object) || type == typeof(TimeSpan) || type == typeof(byte[]) || type==typeof(Uri)) // special primitives
614 if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>))
615 return IsPrimitiveNotEnum (type.GetGenericArguments () [0]);
619 bool ShouldNotRegister (Type type)
621 return IsPrimitiveNotEnum (type);
624 internal bool TryRegister (Type type)
626 // exclude predefined maps
627 if (ShouldNotRegister (type))
630 if (FindUserMap (type) != null)
633 if (RegisterEnum (type) != null)
636 if (RegisterDictionary (type) != null)
639 if (RegisterCollectionContract (type) != null)
642 if (RegisterContract (type) != null)
645 if (RegisterIXmlSerializable (type) != null)
648 if (RegisterCollection (type) != null)
651 if (GetAttribute<SerializableAttribute> (type) != null) {
652 RegisterSerializable (type);
656 RegisterDefaultTypeMap (type);
660 static Type GetCollectionElementType (Type type)
663 return type.GetElementType ();
665 Type [] ifaces = type.GetInterfaces ();
666 foreach (Type i in ifaces)
667 if (i.IsGenericType && i.GetGenericTypeDefinition ().Equals (typeof (ICollection<>)))
668 return i.GetGenericArguments () [0];
669 foreach (Type i in ifaces)
670 if (i == typeof (IList))
671 return typeof (object);
675 internal static T GetAttribute<T> (ICustomAttributeProvider ap) where T : Attribute
677 object [] atts = ap.GetCustomAttributes (typeof (T), false);
678 return atts.Length == 0 ? null : (T) atts [0];
681 private CollectionContractTypeMap RegisterCollectionContract (Type type)
683 var cdca = GetAttribute<CollectionDataContractAttribute> (type);
687 Type element = GetCollectionElementType (type);
689 throw new InvalidOperationException (String.Format ("Type '{0}' is marked as collection contract, but it is not a collection", type));
691 TryRegister (element); // must be registered before the name conflict check.
693 QName qname = GetCollectionContractQName (type);
694 CheckStandardQName (qname);
695 if (FindUserMap (qname) != null)
696 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));
698 var ret = new CollectionContractTypeMap (type, cdca, element, qname, this);
703 private CollectionTypeMap RegisterCollection (Type type)
705 Type element = GetCollectionElementType (type);
709 TryRegister (element);
711 QName qname = GetCollectionQName (element);
713 var map = FindUserMap (qname);
715 var cmap = map as CollectionTypeMap;
716 if (cmap == null || cmap.RuntimeType != type)
717 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));
721 CollectionTypeMap ret =
722 new CollectionTypeMap (type, element, qname, this);
727 static bool TypeImplementsIDictionary (Type type)
729 foreach (var iface in type.GetInterfaces ())
730 if (iface == typeof (IDictionary) || (iface.IsGenericType && iface.GetGenericTypeDefinition () == typeof (IDictionary<,>)))
736 // it also supports contract-based dictionary.
737 private DictionaryTypeMap RegisterDictionary (Type type)
739 if (!TypeImplementsIDictionary (type))
742 var cdca = GetAttribute<CollectionDataContractAttribute> (type);
744 DictionaryTypeMap ret =
745 new DictionaryTypeMap (type, cdca, this);
747 if (FindUserMap (ret.XmlName) != null)
748 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));
751 TryRegister (ret.KeyType);
752 TryRegister (ret.ValueType);
757 private SerializationMap RegisterSerializable (Type type)
759 QName qname = GetSerializableQName (type);
761 if (FindUserMap (qname) != null)
762 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
764 SharedTypeMap ret = new SharedTypeMap (type, qname, this);
770 private SerializationMap RegisterIXmlSerializable (Type type)
772 if (type.GetInterface ("System.Xml.Serialization.IXmlSerializable") == null)
775 QName qname = GetSerializableQName (type);
777 if (FindUserMap (qname) != null)
778 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
780 XmlSerializableMap ret = new XmlSerializableMap (type, qname, this);
786 void CheckStandardQName (QName qname)
788 switch (qname.Namespace) {
789 case XmlSchema.Namespace:
790 case XmlSchema.InstanceNamespace:
791 case MSSimpleNamespace:
792 case MSArraysNamespace:
793 throw new InvalidOperationException (String.Format ("Namespace {0} is reserved and cannot be used for user serialization", qname.Namespace));
798 private SharedContractMap RegisterContract (Type type)
800 QName qname = GetContractQName (type);
803 CheckStandardQName (qname);
804 if (FindUserMap (qname) != null)
805 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
807 SharedContractMap ret = new SharedContractMap (type, qname, this);
811 if (type.BaseType != typeof (object)) {
812 TryRegister (type.BaseType);
813 if (!FindUserMap (type.BaseType).IsContractAllowedType)
814 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));
817 object [] attrs = type.GetCustomAttributes (typeof (KnownTypeAttribute), true);
818 for (int i = 0; i < attrs.Length; i++) {
819 KnownTypeAttribute kt = (KnownTypeAttribute) attrs [i];
820 TryRegister (kt.Type);
826 DefaultTypeMap RegisterDefaultTypeMap (Type type)
828 DefaultTypeMap ret = new DefaultTypeMap (type, this);
834 private EnumMap RegisterEnum (Type type)
836 QName qname = GetEnumQName (type);
840 if (FindUserMap (qname) != null)
841 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
844 new EnumMap (type, qname, this);