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 Type 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 internal static Type GetPrimitiveTypeFromName (string name)
275 return typeof (bool);
277 return typeof (byte []);
279 return typeof (DateTime);
281 return typeof (TimeSpan);
283 return typeof (QName);
285 return typeof (decimal);
287 return typeof (double);
289 return typeof (float);
291 return typeof (sbyte);
293 return typeof (short);
297 return typeof (long);
299 return typeof (byte);
300 case "unsignedShort":
301 return typeof (ushort);
303 return typeof (uint);
305 return typeof (ulong);
307 return typeof (string);
309 return typeof (object);
311 return typeof (Guid);
313 return typeof (char);
320 internal static object PredefinedTypeStringToObject (string s,
321 string name, XmlReader reader)
325 return new Uri(s,UriKind.RelativeOrAbsolute);
327 return XmlConvert.ToBoolean (s);
329 return Convert.FromBase64String (s);
331 return XmlConvert.ToDateTime (s, XmlDateTimeSerializationMode.RoundtripKind);
333 return XmlConvert.ToTimeSpan (s);
335 int idx = s.IndexOf (':');
336 string l = idx < 0 ? s : s.Substring (idx + 1);
337 return idx < 0 ? new QName (l) :
338 new QName (l, reader.LookupNamespace (
339 s.Substring (0, idx)));
341 return XmlConvert.ToDecimal (s);
343 return XmlConvert.ToDouble (s);
345 return XmlConvert.ToSingle (s);
347 return XmlConvert.ToSByte (s);
349 return XmlConvert.ToInt16 (s);
351 return XmlConvert.ToInt32 (s);
353 return XmlConvert.ToInt64 (s);
355 return XmlConvert.ToByte (s);
356 case "unsignedShort":
357 return XmlConvert.ToUInt16 (s);
359 return XmlConvert.ToUInt32 (s);
361 return XmlConvert.ToUInt64 (s);
365 return XmlConvert.ToGuid (s);
369 return (char) XmlConvert.ToUInt32 (s);
371 throw new Exception ("Unanticipated primitive type: " + name);
375 List<SerializationMap> contracts = new List<SerializationMap> ();
377 public KnownTypeCollection ()
381 protected override void ClearItems ()
386 protected override void InsertItem (int index, Type type)
388 if (TryRegister (type))
389 base.InsertItem (index, type);
392 // FIXME: it could remove other types' dependencies.
393 protected override void RemoveItem (int index)
395 Type t = base [index];
396 List<SerializationMap> l = new List<SerializationMap> ();
397 foreach (SerializationMap m in contracts) {
398 if (m.RuntimeType == t)
401 foreach (SerializationMap m in l) {
402 contracts.Remove (m);
403 base.RemoveItem (index);
407 protected override void SetItem (int index, Type type)
410 InsertItem (index, type);
413 if (TryRegister (type))
414 base.InsertItem (index - 1, type);
418 internal SerializationMap FindUserMap (QName qname)
420 for (int i = 0; i < contracts.Count; i++)
421 if (qname == contracts [i].XmlName)
422 return contracts [i];
426 internal Type GetSerializedType (Type type)
428 Type element = GetCollectionElementType (type);
431 QName name = GetQName (type);
432 var map = FindUserMap (name);
434 return map.RuntimeType;
438 internal SerializationMap FindUserMap (Type type)
440 for (int i = 0; i < contracts.Count; i++)
441 if (type == contracts [i].RuntimeType)
442 return contracts [i];
446 internal QName GetQName (Type type)
448 if (IsPrimitiveNotEnum (type))
449 return GetPrimitiveTypeName (type);
451 SerializationMap map = FindUserMap (type);
457 return GetEnumQName (type);
459 QName qname = GetContractQName (type);
463 if (type.GetInterface ("System.Xml.Serialization.IXmlSerializable") != null)
464 //FIXME: Reusing GetSerializableQName here, since we just
465 //need name of the type..
466 return GetSerializableQName (type);
468 qname = GetCollectionContractQName (type);
472 Type element = GetCollectionElementType (type);
474 return GetCollectionQName (element);
476 if (GetAttribute<SerializableAttribute> (type) != null)
477 return GetSerializableQName (type);
479 // FIXME: it needs in-depth check.
483 QName GetContractQName (Type type)
485 var a = GetAttribute<DataContractAttribute> (type);
486 return a == null ? null : GetContractQName (type, a.Name, a.Namespace);
489 QName GetCollectionContractQName (Type type)
491 var a = GetAttribute<CollectionDataContractAttribute> (type);
492 return a == null ? null : GetContractQName (type, a.Name, a.Namespace);
495 internal static QName GetContractQName (Type type, string name, string ns)
498 // FIXME: there could be decent ways to get
499 // the same result...
500 name = type.Namespace == null || type.Namespace.Length == 0 ? type.Name : type.FullName.Substring (type.Namespace.Length + 1).Replace ('+', '.');
501 if (type.IsGenericType) {
502 name = name.Substring (0, name.IndexOf ('`')) + "Of";
503 foreach (var t in type.GetGenericArguments ())
504 name += t.Name; // FIXME: check namespaces too
508 ns = DefaultClrNamespaceBase + type.Namespace;
509 return new QName (name, ns);
512 private QName GetEnumQName (Type type)
514 string name = null, ns = null;
519 var dca = GetAttribute<DataContractAttribute> (type);
527 ns = DefaultClrNamespaceBase + type.Namespace;
530 name = type.Namespace == null ? type.Name : type.FullName.Substring (type.Namespace.Length + 1).Replace ('+', '.');
532 return new QName (name, ns);
535 private QName GetCollectionQName (Type element)
537 QName eqname = GetQName (element);
539 string ns = eqname.Namespace;
540 if (eqname.Namespace == MSSimpleNamespace)
541 //Arrays of Primitive types
542 ns = MSArraysNamespace;
545 "ArrayOf" + XmlConvert.EncodeLocalName (eqname.Name),
549 private QName GetSerializableQName (Type type)
551 string xmlName = type.Name;
552 if (type.IsGenericType) {
553 xmlName = xmlName.Substring (0, xmlName.IndexOf ('`')) + "Of";
554 foreach (var t in type.GetGenericArguments ())
555 xmlName += GetQName (t).Name; // FIXME: check namespaces too
557 string xmlNamespace = DefaultClrNamespaceBase + type.Namespace;
558 var x = GetAttribute<XmlRootAttribute> (type);
560 xmlName = x.ElementName;
561 xmlNamespace = x.Namespace;
563 return new QName (XmlConvert.EncodeLocalName (xmlName), xmlNamespace);
566 internal bool IsPrimitiveNotEnum (Type type)
570 if (Type.GetTypeCode (type) != TypeCode.Object) // explicitly primitive
572 if (type == typeof (Guid) || type == typeof (object) || type == typeof(TimeSpan) || type == typeof(byte[]) || type==typeof(Uri)) // special primitives
575 if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>))
576 return IsPrimitiveNotEnum (type.GetGenericArguments () [0]);
580 internal bool TryRegister (Type type)
582 // exclude predefined maps
583 if (IsPrimitiveNotEnum (type))
586 if (FindUserMap (type) != null)
589 if (RegisterEnum (type) != null)
592 if (RegisterContract (type) != null)
595 if (RegisterIXmlSerializable (type) != null)
598 if (RegisterDictionary (type) != null)
601 if (RegisterCollectionContract (type) != null)
604 if (RegisterCollection (type) != null)
607 if (GetAttribute<SerializableAttribute> (type) != null) {
608 RegisterSerializable (type);
612 RegisterDefaultTypeMap (type);
616 internal static Type GetCollectionElementType (Type type)
619 return type.GetElementType ();
621 Type [] ifaces = type.GetInterfaces ();
622 foreach (Type i in ifaces)
623 if (i.IsGenericType && i.GetGenericTypeDefinition ().Equals (typeof (ICollection<>)))
624 return i.GetGenericArguments () [0];
625 foreach (Type i in ifaces)
626 if (i == typeof (IList))
627 return typeof (object);
631 internal T GetAttribute<T> (MemberInfo mi) where T : Attribute
633 object [] atts = mi.GetCustomAttributes (typeof (T), false);
634 return atts.Length == 0 ? null : (T) atts [0];
637 private CollectionContractTypeMap RegisterCollectionContract (Type type)
639 var cdca = GetAttribute<CollectionDataContractAttribute> (type);
643 Type element = GetCollectionElementType (type);
645 throw new InvalidOperationException (String.Format ("Type '{0}' is marked as collection contract, but it is not a collection", type));
647 TryRegister (element); // must be registered before the name conflict check.
649 QName qname = GetCollectionContractQName (type);
650 CheckStandardQName (qname);
651 if (FindUserMap (qname) != null)
652 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));
654 var ret = new CollectionContractTypeMap (type, cdca, element, qname, this);
659 private CollectionTypeMap RegisterCollection (Type type)
661 Type element = GetCollectionElementType (type);
665 TryRegister (element);
667 QName qname = GetCollectionQName (element);
669 var map = FindUserMap (qname);
671 var cmap = map as CollectionTypeMap;
672 if (cmap == null || cmap.RuntimeType != type)
673 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));
677 CollectionTypeMap ret =
678 new CollectionTypeMap (type, element, qname, this);
683 static bool TypeImplementsIDictionary (Type type)
685 foreach (var iface in type.GetInterfaces ())
686 if (iface == typeof (IDictionary) || (iface.IsGenericType && iface.GetGenericTypeDefinition () == typeof (IDictionary<,>)))
692 // it also supports contract-based dictionary.
693 private DictionaryTypeMap RegisterDictionary (Type type)
695 if (!TypeImplementsIDictionary (type))
698 var cdca = GetAttribute<CollectionDataContractAttribute> (type);
700 DictionaryTypeMap ret =
701 new DictionaryTypeMap (type, cdca, this);
703 if (FindUserMap (ret.XmlName) != null)
704 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));
707 TryRegister (ret.KeyType);
708 TryRegister (ret.ValueType);
713 private SerializationMap RegisterSerializable (Type type)
715 QName qname = GetSerializableQName (type);
717 if (FindUserMap (qname) != null)
718 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
721 new SharedTypeMap (type, qname, this);
726 private SerializationMap RegisterIXmlSerializable (Type type)
728 if (type.GetInterface ("System.Xml.Serialization.IXmlSerializable") == null)
731 QName qname = GetSerializableQName (type);
733 if (FindUserMap (qname) != null)
734 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
736 XmlSerializableMap ret = new XmlSerializableMap (type, qname, this);
742 void CheckStandardQName (QName qname)
744 switch (qname.Namespace) {
745 case XmlSchema.Namespace:
746 case XmlSchema.InstanceNamespace:
747 case MSSimpleNamespace:
748 case MSArraysNamespace:
749 throw new InvalidOperationException (String.Format ("Namespace {0} is reserved and cannot be used for user serialization", qname.Namespace));
754 private SharedContractMap RegisterContract (Type type)
756 QName qname = GetContractQName (type);
759 CheckStandardQName (qname);
760 if (FindUserMap (qname) != null)
761 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
763 SharedContractMap ret = new SharedContractMap (type, qname, this);
767 object [] attrs = type.GetCustomAttributes (typeof (KnownTypeAttribute), true);
768 for (int i = 0; i < attrs.Length; i++) {
769 KnownTypeAttribute kt = (KnownTypeAttribute) attrs [i];
770 TryRegister (kt.Type);
776 DefaultTypeMap RegisterDefaultTypeMap (Type type)
778 DefaultTypeMap ret = new DefaultTypeMap (type, this);
783 private EnumMap RegisterEnum (Type type)
785 QName qname = GetEnumQName (type);
789 if (FindUserMap (qname) != null)
790 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
793 new EnumMap (type, qname, this);