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 internal 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 name
= GetDefaultName (type
);
505 ns
= GetDefaultNamespace (type
);
506 return new QName (name
, ns
);
509 static QName
GetEnumQName (Type type
)
511 string name
= null, ns
= null;
516 var dca
= GetAttribute
<DataContractAttribute
> (type
);
524 ns
= GetDefaultNamespace (type
);
527 name
= type
.Namespace
== null ? type
.Name
: type
.FullName
.Substring (type
.Namespace
.Length
+ 1).Replace ('+', '.');
529 return new QName (name
, ns
);
532 internal static string GetDefaultName (Type type
)
534 // FIXME: there could be decent ways to get
535 // the same result...
536 string name
= type
.Namespace
== null || type
.Namespace
.Length
== 0 ? type
.Name
: type
.FullName
.Substring (type
.Namespace
.Length
+ 1).Replace ('+', '.');
537 if (type
.IsGenericType
) {
538 name
= name
.Substring (0, name
.IndexOf ('`')) + "Of";
539 foreach (var t
in type
.GetGenericArguments ())
540 name
+= t
.Name
; // FIXME: check namespaces too
545 internal static string GetDefaultNamespace (Type type
)
547 foreach (ContractNamespaceAttribute a
in type
.Assembly
.GetCustomAttributes (typeof (ContractNamespaceAttribute
), true))
548 if (a
.ClrNamespace
== type
.Namespace
)
549 return a
.ContractNamespace
;
550 return DefaultClrNamespaceBase
+ type
.Namespace
;
553 static QName
GetCollectionQName (Type element
)
555 QName eqname
= GetStaticQName (element
);
557 string ns
= eqname
.Namespace
;
558 if (eqname
.Namespace
== MSSimpleNamespace
)
559 //Arrays of Primitive types
560 ns
= MSArraysNamespace
;
563 "ArrayOf" + XmlConvert
.EncodeLocalName (eqname
.Name
),
567 static QName
GetSerializableQName (Type type
)
569 string xmlName
= type
.Name
;
570 if (type
.IsGenericType
) {
571 xmlName
= xmlName
.Substring (0, xmlName
.IndexOf ('`')) + "Of";
572 foreach (var t
in type
.GetGenericArguments ())
573 xmlName
+= GetStaticQName (t
).Name
; // FIXME: check namespaces too
575 string xmlNamespace
= GetDefaultNamespace (type
);
576 var x
= GetAttribute
<XmlRootAttribute
> (type
);
578 xmlName
= x
.ElementName
;
579 xmlNamespace
= x
.Namespace
;
581 return new QName (XmlConvert
.EncodeLocalName (xmlName
), xmlNamespace
);
584 static bool IsPrimitiveNotEnum (Type type
)
588 if (Type
.GetTypeCode (type
) != TypeCode
.Object
) // explicitly primitive
590 if (type
== typeof (Guid
) || type
== typeof (object) || type
== typeof(TimeSpan
) || type
== typeof(byte[]) || type
==typeof(Uri
)) // special primitives
593 if (type
.IsGenericType
&& type
.GetGenericTypeDefinition () == typeof (Nullable
<>))
594 return IsPrimitiveNotEnum (type
.GetGenericArguments () [0]);
598 internal bool TryRegister (Type type
)
600 // exclude predefined maps
601 if (IsPrimitiveNotEnum (type
))
604 if (FindUserMap (type
) != null)
607 if (RegisterEnum (type
) != null)
610 if (RegisterDictionary (type
) != null)
613 if (RegisterCollectionContract (type
) != null)
616 if (RegisterContract (type
) != null)
619 if (RegisterIXmlSerializable (type
) != null)
622 if (RegisterCollection (type
) != null)
625 if (GetAttribute
<SerializableAttribute
> (type
) != null) {
626 RegisterSerializable (type
);
630 RegisterDefaultTypeMap (type
);
634 static Type
GetCollectionElementType (Type type
)
637 return type
.GetElementType ();
639 Type
[] ifaces
= type
.GetInterfaces ();
640 foreach (Type i
in ifaces
)
641 if (i
.IsGenericType
&& i
.GetGenericTypeDefinition ().Equals (typeof (ICollection
<>)))
642 return i
.GetGenericArguments () [0];
643 foreach (Type i
in ifaces
)
644 if (i
== typeof (IList
))
645 return typeof (object);
649 internal static T GetAttribute
<T
> (ICustomAttributeProvider ap
) where T
: Attribute
651 object [] atts
= ap
.GetCustomAttributes (typeof (T
), false);
652 return atts
.Length
== 0 ? null : (T
) atts
[0];
655 private CollectionContractTypeMap
RegisterCollectionContract (Type type
)
657 var cdca
= GetAttribute
<CollectionDataContractAttribute
> (type
);
661 Type element
= GetCollectionElementType (type
);
663 throw new InvalidOperationException (String
.Format ("Type '{0}' is marked as collection contract, but it is not a collection", type
));
665 TryRegister (element
); // must be registered before the name conflict check.
667 QName qname
= GetCollectionContractQName (type
);
668 CheckStandardQName (qname
);
669 if (FindUserMap (qname
) != null)
670 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
));
672 var ret
= new CollectionContractTypeMap (type
, cdca
, element
, qname
, this);
677 private CollectionTypeMap
RegisterCollection (Type type
)
679 Type element
= GetCollectionElementType (type
);
683 TryRegister (element
);
685 QName qname
= GetCollectionQName (element
);
687 var map
= FindUserMap (qname
);
689 var cmap
= map
as CollectionTypeMap
;
690 if (cmap
== null || cmap
.RuntimeType
!= type
)
691 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
));
695 CollectionTypeMap ret
=
696 new CollectionTypeMap (type
, element
, qname
, this);
701 static bool TypeImplementsIDictionary (Type type
)
703 foreach (var iface
in type
.GetInterfaces ())
704 if (iface
== typeof (IDictionary
) || (iface
.IsGenericType
&& iface
.GetGenericTypeDefinition () == typeof (IDictionary
<,>)))
710 // it also supports contract-based dictionary.
711 private DictionaryTypeMap
RegisterDictionary (Type type
)
713 if (!TypeImplementsIDictionary (type
))
716 var cdca
= GetAttribute
<CollectionDataContractAttribute
> (type
);
718 DictionaryTypeMap ret
=
719 new DictionaryTypeMap (type
, cdca
, this);
721 if (FindUserMap (ret
.XmlName
) != null)
722 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
));
725 TryRegister (ret
.KeyType
);
726 TryRegister (ret
.ValueType
);
731 private SerializationMap
RegisterSerializable (Type type
)
733 QName qname
= GetSerializableQName (type
);
735 if (FindUserMap (qname
) != null)
736 throw new InvalidOperationException (String
.Format ("There is already a registered type for XML name {0}", qname
));
738 SharedTypeMap ret
= new SharedTypeMap (type
, qname
, this);
744 private SerializationMap
RegisterIXmlSerializable (Type type
)
746 if (type
.GetInterface ("System.Xml.Serialization.IXmlSerializable") == null)
749 QName qname
= GetSerializableQName (type
);
751 if (FindUserMap (qname
) != null)
752 throw new InvalidOperationException (String
.Format ("There is already a registered type for XML name {0}", qname
));
754 XmlSerializableMap ret
= new XmlSerializableMap (type
, qname
, this);
760 void CheckStandardQName (QName qname
)
762 switch (qname
.Namespace
) {
763 case XmlSchema
.Namespace
:
764 case XmlSchema
.InstanceNamespace
:
765 case MSSimpleNamespace
:
766 case MSArraysNamespace
:
767 throw new InvalidOperationException (String
.Format ("Namespace {0} is reserved and cannot be used for user serialization", qname
.Namespace
));
772 private SharedContractMap
RegisterContract (Type type
)
774 QName qname
= GetContractQName (type
);
777 CheckStandardQName (qname
);
778 if (FindUserMap (qname
) != null)
779 throw new InvalidOperationException (String
.Format ("There is already a registered type for XML name {0}", qname
));
781 SharedContractMap ret
= new SharedContractMap (type
, qname
, this);
785 object [] attrs
= type
.GetCustomAttributes (typeof (KnownTypeAttribute
), true);
786 for (int i
= 0; i
< attrs
.Length
; i
++) {
787 KnownTypeAttribute kt
= (KnownTypeAttribute
) attrs
[i
];
788 TryRegister (kt
.Type
);
794 DefaultTypeMap
RegisterDefaultTypeMap (Type type
)
796 DefaultTypeMap ret
= new DefaultTypeMap (type
, this);
801 private EnumMap
RegisterEnum (Type type
)
803 QName qname
= GetEnumQName (type
);
807 if (FindUserMap (qname
) != null)
808 throw new InvalidOperationException (String
.Format ("There is already a registered type for XML name {0}", qname
));
811 new EnumMap (type
, qname
, this);