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 // 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 (TryRegister (type
))
390 base.InsertItem (index
, type
);
393 // FIXME: it could remove other types' dependencies.
394 protected override void RemoveItem (int index
)
396 Type t
= base [index
];
397 List
<SerializationMap
> l
= new List
<SerializationMap
> ();
398 foreach (SerializationMap m
in contracts
) {
399 if (m
.RuntimeType
== t
)
402 foreach (SerializationMap m
in l
) {
403 contracts
.Remove (m
);
404 base.RemoveItem (index
);
408 protected override void SetItem (int index
, Type type
)
411 InsertItem (index
, type
);
414 if (TryRegister (type
))
415 base.InsertItem (index
- 1, type
);
419 internal SerializationMap
FindUserMap (QName qname
)
421 for (int i
= 0; i
< contracts
.Count
; i
++)
422 if (qname
== contracts
[i
].XmlName
)
423 return contracts
[i
];
427 internal Type
GetSerializedType (Type type
)
429 Type element
= GetCollectionElementType (type
);
432 QName name
= GetQName (type
);
433 var map
= FindUserMap (name
);
435 return map
.RuntimeType
;
439 internal SerializationMap
FindUserMap (Type type
)
441 for (int i
= 0; i
< contracts
.Count
; i
++)
442 if (type
== contracts
[i
].RuntimeType
)
443 return contracts
[i
];
447 internal QName
GetQName (Type type
)
449 SerializationMap map
= FindUserMap (type
);
453 return GetStaticQName (type
);
456 public static QName
GetStaticQName (Type type
)
458 if (IsPrimitiveNotEnum (type
))
459 return GetPrimitiveTypeName (type
);
462 return GetEnumQName (type
);
464 QName qname
= GetContractQName (type
);
468 if (type
.GetInterface ("System.Xml.Serialization.IXmlSerializable") != null)
469 //FIXME: Reusing GetSerializableQName here, since we just
470 //need name of the type..
471 return GetSerializableQName (type
);
473 qname
= GetCollectionContractQName (type
);
477 Type element
= GetCollectionElementType (type
);
479 return GetCollectionQName (element
);
481 if (GetAttribute
<SerializableAttribute
> (type
) != null)
482 return GetSerializableQName (type
);
484 // default type map - still uses GetContractQName().
485 return GetContractQName (type
, null, null);
488 static QName
GetContractQName (Type type
)
490 var a
= GetAttribute
<DataContractAttribute
> (type
);
491 return a
== null ? null : GetContractQName (type
, a
.Name
, a
.Namespace
);
494 static QName
GetCollectionContractQName (Type type
)
496 var a
= GetAttribute
<CollectionDataContractAttribute
> (type
);
497 return a
== null ? null : GetContractQName (type
, a
.Name
, a
.Namespace
);
500 static QName
GetContractQName (Type type
, string name
, string ns
)
503 // FIXME: there could be decent ways to get
504 // the same result...
505 name
= type
.Namespace
== null || type
.Namespace
.Length
== 0 ? type
.Name
: type
.FullName
.Substring (type
.Namespace
.Length
+ 1).Replace ('+', '.');
506 if (type
.IsGenericType
) {
507 name
= name
.Substring (0, name
.IndexOf ('`')) + "Of";
508 foreach (var t
in type
.GetGenericArguments ())
509 name
+= t
.Name
; // FIXME: check namespaces too
513 ns
= DefaultClrNamespaceBase
+ type
.Namespace
;
514 return new QName (name
, ns
);
517 static QName
GetEnumQName (Type type
)
519 string name
= null, ns
= null;
524 var dca
= GetAttribute
<DataContractAttribute
> (type
);
532 ns
= DefaultClrNamespaceBase
+ type
.Namespace
;
535 name
= type
.Namespace
== null ? type
.Name
: type
.FullName
.Substring (type
.Namespace
.Length
+ 1).Replace ('+', '.');
537 return new QName (name
, ns
);
540 static QName
GetCollectionQName (Type element
)
542 QName eqname
= GetStaticQName (element
);
544 string ns
= eqname
.Namespace
;
545 if (eqname
.Namespace
== MSSimpleNamespace
)
546 //Arrays of Primitive types
547 ns
= MSArraysNamespace
;
550 "ArrayOf" + XmlConvert
.EncodeLocalName (eqname
.Name
),
554 static QName
GetSerializableQName (Type type
)
556 string xmlName
= type
.Name
;
557 if (type
.IsGenericType
) {
558 xmlName
= xmlName
.Substring (0, xmlName
.IndexOf ('`')) + "Of";
559 foreach (var t
in type
.GetGenericArguments ())
560 xmlName
+= GetStaticQName (t
).Name
; // FIXME: check namespaces too
562 string xmlNamespace
= DefaultClrNamespaceBase
+ type
.Namespace
;
563 var x
= GetAttribute
<XmlRootAttribute
> (type
);
565 xmlName
= x
.ElementName
;
566 xmlNamespace
= x
.Namespace
;
568 return new QName (XmlConvert
.EncodeLocalName (xmlName
), xmlNamespace
);
571 static bool IsPrimitiveNotEnum (Type type
)
575 if (Type
.GetTypeCode (type
) != TypeCode
.Object
) // explicitly primitive
577 if (type
== typeof (Guid
) || type
== typeof (object) || type
== typeof(TimeSpan
) || type
== typeof(byte[]) || type
==typeof(Uri
)) // special primitives
580 if (type
.IsGenericType
&& type
.GetGenericTypeDefinition () == typeof (Nullable
<>))
581 return IsPrimitiveNotEnum (type
.GetGenericArguments () [0]);
585 internal bool TryRegister (Type type
)
587 // exclude predefined maps
588 if (IsPrimitiveNotEnum (type
))
591 if (FindUserMap (type
) != null)
594 if (RegisterEnum (type
) != null)
597 if (RegisterContract (type
) != null)
600 if (RegisterIXmlSerializable (type
) != null)
603 if (RegisterDictionary (type
) != null)
606 if (RegisterCollectionContract (type
) != null)
609 if (RegisterCollection (type
) != null)
612 if (GetAttribute
<SerializableAttribute
> (type
) != null) {
613 RegisterSerializable (type
);
617 RegisterDefaultTypeMap (type
);
621 static Type
GetCollectionElementType (Type type
)
624 return type
.GetElementType ();
626 Type
[] ifaces
= type
.GetInterfaces ();
627 foreach (Type i
in ifaces
)
628 if (i
.IsGenericType
&& i
.GetGenericTypeDefinition ().Equals (typeof (ICollection
<>)))
629 return i
.GetGenericArguments () [0];
630 foreach (Type i
in ifaces
)
631 if (i
== typeof (IList
))
632 return typeof (object);
636 internal static T GetAttribute
<T
> (MemberInfo mi
) where T
: Attribute
638 object [] atts
= mi
.GetCustomAttributes (typeof (T
), false);
639 return atts
.Length
== 0 ? null : (T
) atts
[0];
642 private CollectionContractTypeMap
RegisterCollectionContract (Type type
)
644 var cdca
= GetAttribute
<CollectionDataContractAttribute
> (type
);
648 Type element
= GetCollectionElementType (type
);
650 throw new InvalidOperationException (String
.Format ("Type '{0}' is marked as collection contract, but it is not a collection", type
));
652 TryRegister (element
); // must be registered before the name conflict check.
654 QName qname
= GetCollectionContractQName (type
);
655 CheckStandardQName (qname
);
656 if (FindUserMap (qname
) != null)
657 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
));
659 var ret
= new CollectionContractTypeMap (type
, cdca
, element
, qname
, this);
664 private CollectionTypeMap
RegisterCollection (Type type
)
666 Type element
= GetCollectionElementType (type
);
670 TryRegister (element
);
672 QName qname
= GetCollectionQName (element
);
674 var map
= FindUserMap (qname
);
676 var cmap
= map
as CollectionTypeMap
;
677 if (cmap
== null || cmap
.RuntimeType
!= type
)
678 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
));
682 CollectionTypeMap ret
=
683 new CollectionTypeMap (type
, element
, qname
, this);
688 static bool TypeImplementsIDictionary (Type type
)
690 foreach (var iface
in type
.GetInterfaces ())
691 if (iface
== typeof (IDictionary
) || (iface
.IsGenericType
&& iface
.GetGenericTypeDefinition () == typeof (IDictionary
<,>)))
697 // it also supports contract-based dictionary.
698 private DictionaryTypeMap
RegisterDictionary (Type type
)
700 if (!TypeImplementsIDictionary (type
))
703 var cdca
= GetAttribute
<CollectionDataContractAttribute
> (type
);
705 DictionaryTypeMap ret
=
706 new DictionaryTypeMap (type
, cdca
, this);
708 if (FindUserMap (ret
.XmlName
) != null)
709 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
));
712 TryRegister (ret
.KeyType
);
713 TryRegister (ret
.ValueType
);
718 private SerializationMap
RegisterSerializable (Type type
)
720 QName qname
= GetSerializableQName (type
);
722 if (FindUserMap (qname
) != null)
723 throw new InvalidOperationException (String
.Format ("There is already a registered type for XML name {0}", qname
));
725 SharedTypeMap ret
= new SharedTypeMap (type
, qname
, this);
731 private SerializationMap
RegisterIXmlSerializable (Type type
)
733 if (type
.GetInterface ("System.Xml.Serialization.IXmlSerializable") == null)
736 QName qname
= GetSerializableQName (type
);
738 if (FindUserMap (qname
) != null)
739 throw new InvalidOperationException (String
.Format ("There is already a registered type for XML name {0}", qname
));
741 XmlSerializableMap ret
= new XmlSerializableMap (type
, qname
, this);
747 void CheckStandardQName (QName qname
)
749 switch (qname
.Namespace
) {
750 case XmlSchema
.Namespace
:
751 case XmlSchema
.InstanceNamespace
:
752 case MSSimpleNamespace
:
753 case MSArraysNamespace
:
754 throw new InvalidOperationException (String
.Format ("Namespace {0} is reserved and cannot be used for user serialization", qname
.Namespace
));
759 private SharedContractMap
RegisterContract (Type type
)
761 QName qname
= GetContractQName (type
);
764 CheckStandardQName (qname
);
765 if (FindUserMap (qname
) != null)
766 throw new InvalidOperationException (String
.Format ("There is already a registered type for XML name {0}", qname
));
768 SharedContractMap ret
= new SharedContractMap (type
, qname
, this);
772 object [] attrs
= type
.GetCustomAttributes (typeof (KnownTypeAttribute
), true);
773 for (int i
= 0; i
< attrs
.Length
; i
++) {
774 KnownTypeAttribute kt
= (KnownTypeAttribute
) attrs
[i
];
775 TryRegister (kt
.Type
);
781 DefaultTypeMap
RegisterDefaultTypeMap (Type type
)
783 DefaultTypeMap ret
= new DefaultTypeMap (type
, this);
788 private EnumMap
RegisterEnum (Type type
)
790 QName qname
= GetEnumQName (type
);
794 if (FindUserMap (qname
) != null)
795 throw new InvalidOperationException (String
.Format ("There is already a registered type for XML name {0}", qname
));
798 new EnumMap (type
, qname
, this);