5 // Atsushi Enomoto <atsushi@ximian.com>
6 // Ankit Jain <JAnkit@novell.com>
7 // Duncan Mak (duncan@ximian.com)
8 // Eyal Alaluf (eyala@mainsoft.com)
10 // Copyright (C) 2005 Novell, Inc. http://www.novell.com
11 // Copyright (C) 2006 Novell, Inc. http://www.novell.com
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 using System.Collections;
35 using System.Collections.Generic;
36 using System.Collections.ObjectModel;
37 using System.Reflection;
39 using System.Xml.Schema;
40 using System.Xml.Serialization;
42 using QName = System.Xml.XmlQualifiedName;
44 namespace System.Runtime.Serialization
47 XmlFormatter implementation design inference:
50 - No XML Schema types are directly used. There are some maps from
51 xs:blahType to ms:blahType where the namespaceURI for prefix "ms" is
52 "http://schemas.microsoft.com/2003/10/Serialization/" .
55 - An object being serialized 1) must be of type System.Object, or
56 2) must be null, or 3) must have either a [DataContract] attribute
57 or a [Serializable] attribute to be serializable.
58 - When the object is either of type System.Object or null, then the
59 XML type is "anyType".
60 - When the object is [Serializable], then the runtime-serialization
61 compatible object graph is written.
62 - Otherwise the serialization is based on contract attributes.
63 ([Serializable] takes precedence).
66 - For type A to be serializable, the base type B of A must be
68 - If a type which is [Serializable] and whose base type has a
69 [DataContract], then for base type members [DataContract] is taken.
70 - It is vice versa i.e. if the base type is [Serializable] and the
71 derived type has a [DataContract], then [Serializable] takes place
74 known type collection:
75 - It internally manages mapping store keyed by contract QNames.
76 KnownTypeCollection.Add() checks if the same QName contract already
77 exists (and raises InvalidOperationException if required).
80 internal abstract class SerializationMap
82 public const BindingFlags AllInstanceFlags =
83 BindingFlags.Public | BindingFlags.NonPublic |
84 BindingFlags.Instance;
86 public readonly KnownTypeCollection KnownTypes;
87 public readonly Type RuntimeType;
88 public readonly QName XmlName;
89 public List<DataMemberInfo> Members;
91 XmlSchemaSet schema_set;
95 Dictionary<Type, QName> qname_table = new Dictionary<Type, QName> ();
97 protected SerializationMap (
98 Type type, QName qname, KnownTypeCollection knownTypes)
100 KnownTypes = knownTypes;
102 if (qname.Namespace == String.Empty)
103 qname = new QName (qname.Name,
104 "http://schemas.datacontract.org/2004/07/" + type.Namespace);
107 Members = new List<DataMemberInfo> ();
110 public DataMemberAttribute GetDataMemberAttribute (
113 object [] atts = mi.GetCustomAttributes (
114 typeof (DataMemberAttribute), false);
115 if (atts.Length == 0)
117 return (DataMemberAttribute) atts [0];
120 bool IsPrimitive (Type type)
122 return (Type.GetTypeCode (type) != TypeCode.Object || type == typeof (object));
126 /* Returns the XmlSchemaType AND adds it to @schemas */
127 public virtual XmlSchemaType GetSchemaType (XmlSchemaSet schemas, Dictionary<QName, XmlSchemaType> generated_schema_types)
129 if (IsPrimitive (RuntimeType))
132 if (generated_schema_types.ContainsKey (XmlName)) // Caching
133 return generated_schema_types [XmlName] as XmlSchemaType;
135 XmlSchemaComplexType complex_type = null;
137 complex_type = new XmlSchemaComplexType ();
138 complex_type.Name = XmlName.Name;
139 generated_schema_types [XmlName] = complex_type;
141 if (RuntimeType.BaseType == typeof (object)) {
142 complex_type.Particle = GetSequence (schemas, generated_schema_types);
144 //Has a non-System.Object base class
145 XmlSchemaComplexContentExtension extension = new XmlSchemaComplexContentExtension ();
146 XmlSchemaComplexContent content = new XmlSchemaComplexContent ();
148 complex_type.ContentModel = content;
149 content.Content = extension;
151 KnownTypes.Add (RuntimeType.BaseType);
152 SerializationMap map = KnownTypes.FindUserMap (RuntimeType.BaseType);
153 //FIXME: map == null ?
154 map.GetSchemaType (schemas, generated_schema_types);
156 extension.Particle = GetSequence (schemas, generated_schema_types);
157 extension.BaseTypeName = GetQualifiedName (RuntimeType.BaseType);
160 XmlSchemaElement schemaElement = GetSchemaElement (XmlName, complex_type);
161 XmlSchema schema = GetSchema (schemas, XmlName.Namespace);
162 schema.Items.Add (complex_type);
163 schema.Items.Add (schemaElement);
164 schemas.Reprocess (schema);
169 /* Returns the <xs:sequence> for the data members */
170 XmlSchemaSequence GetSequence (XmlSchemaSet schemas,
171 Dictionary<QName, XmlSchemaType> generated_schema_types)
173 List<DataMemberInfo> members = GetMembers ();
175 XmlSchema schema = GetSchema (schemas, XmlName.Namespace);
176 XmlSchemaSequence sequence = new XmlSchemaSequence ();
177 foreach (DataMemberInfo dmi in members) {
178 // delegates are not supported.
179 if (!dmi.MemberType.IsAbstract && typeof (System.Delegate).IsAssignableFrom (dmi.MemberType))
182 XmlSchemaElement element = new XmlSchemaElement ();
183 element.Name = dmi.XmlName;
185 KnownTypes.Add (dmi.MemberType);
186 SerializationMap map = KnownTypes.FindUserMap (dmi.MemberType);
188 XmlSchemaType schema_type = map.GetSchemaType (schemas, generated_schema_types);
189 if (schema_type is XmlSchemaComplexType)
190 element.IsNillable = true;
193 if (dmi.MemberType == typeof (string))
194 element.IsNillable = true;
197 element.MinOccurs = 0;
199 element.SchemaTypeName = GetQualifiedName (dmi.MemberType);
200 AddImport (schema, element.SchemaTypeName.Namespace);
202 sequence.Items.Add (element);
205 schemas.Reprocess (schema);
209 //FIXME: Replace with a dictionary ?
210 void AddImport (XmlSchema schema, string ns)
212 if (ns == XmlSchema.Namespace || schema.TargetNamespace == ns)
215 foreach (XmlSchemaObject o in schema.Includes) {
216 XmlSchemaImport import = o as XmlSchemaImport;
219 if (import.Namespace == ns)
223 XmlSchemaImport imp = new XmlSchemaImport ();
225 schema.Includes.Add (imp);
229 //Returns list of data members for this type ONLY
230 public virtual List<DataMemberInfo> GetMembers ()
232 throw new NotImplementedException (String.Format ("Implement me for {0}", this));
236 protected XmlSchemaElement GetSchemaElement (QName qname, XmlSchemaType schemaType)
238 XmlSchemaElement schemaElement = new XmlSchemaElement ();
239 schemaElement.Name = qname.Name;
240 schemaElement.SchemaTypeName = qname;
242 if (schemaType is XmlSchemaComplexType)
243 schemaElement.IsNillable = true;
245 return schemaElement;
248 protected XmlSchema GetSchema (XmlSchemaSet schemas, string ns)
250 ICollection colln = schemas.Schemas (ns);
251 if (colln.Count > 0) {
253 throw new Exception (String.Format (
254 "More than 1 schema for namespace '{0}' found.", ns));
255 foreach (object o in colln)
257 return (o as XmlSchema);
260 XmlSchema schema = new XmlSchema ();
261 schema.TargetNamespace = ns;
262 schema.ElementFormDefault = XmlSchemaForm.Qualified;
263 schemas.Add (schema);
270 protected XmlQualifiedName GetQualifiedName (Type type)
272 if (qname_table.ContainsKey (type))
273 return qname_table [type];
275 QName qname = KnownTypes.GetQName (type);
276 if (qname.Namespace == KnownTypeCollection.MSSimpleNamespace)
277 qname = new QName (qname.Name, XmlSchema.Namespace);
279 qname_table [type] = qname;
283 public virtual void Serialize (object graph,
284 XmlFormatterSerializer serializer)
286 foreach (DataMemberInfo dmi in Members) {
287 FieldInfo fi = dmi.Member as FieldInfo;
288 PropertyInfo pi = fi == null ?
289 (PropertyInfo) dmi.Member : null;
290 Type type = fi != null ?
291 fi.FieldType : pi.PropertyType;
292 object value = fi != null ?
293 fi.GetValue (graph) :
294 pi.GetValue (graph, null);
296 serializer.WriteStartElement (dmi.XmlName, dmi.XmlRootNamespace, dmi.XmlNamespace);
297 serializer.Serialize (type, value);
298 serializer.WriteEndElement ();
302 /* Deserialize non-primitive types */
303 public virtual object DeserializeContent (XmlReader reader,
304 XmlFormatterDeserializer deserializer)
306 object instance = FormatterServices.GetUninitializedObject (RuntimeType);
307 int depth = reader.NodeType == XmlNodeType.None ? reader.Depth : reader.Depth - 1;
308 bool [] filled = new bool [Members.Count];
310 while (reader.NodeType == XmlNodeType.Element && reader.Depth > depth) {
311 DataMemberInfo dmi = null;
312 for (int i = memberInd + 1; i < Members.Count; i++) {
313 if (reader.LocalName == Members [i].XmlName &&
314 reader.NamespaceURI == Members [i].XmlRootNamespace) {
325 SetValue (dmi, instance, deserializer.Deserialize (dmi.MemberType, reader));
326 filled [memberInd] = true;
328 for (int i = 0; i < Members.Count; i++)
329 if (!filled [i] && Members [i].IsRequired)
330 throw MissingRequiredMember (Members [i], reader);
335 // For now it could be private.
336 protected Exception MissingRequiredMember (DataMemberInfo dmi, XmlReader reader)
338 return new ArgumentException (String.Format ("Data contract member {0} is required, but missing in the input XML.", new QName (dmi.XmlName, dmi.XmlNamespace)));
341 // For now it could be private.
342 protected void SetValue (DataMemberInfo dmi, object obj, object value)
344 if (dmi.Member is PropertyInfo)
345 ((PropertyInfo) dmi.Member).SetValue (obj, value, null);
347 ((FieldInfo) dmi.Member).SetValue (obj, value);
350 protected DataMemberInfo CreateDataMemberInfo (DataMemberAttribute dma, MemberInfo mi, Type type)
352 KnownTypes.Add (type);
353 QName qname = KnownTypes.GetQName (type);
354 string rootNamespace = KnownTypes.GetQName (mi.DeclaringType).Namespace;
355 if (KnownTypeCollection.IsPrimitiveType (qname))
356 return new DataMemberInfo (mi, dma, rootNamespace, null);
358 return new DataMemberInfo (mi, dma, rootNamespace, qname.Namespace);
362 internal class XmlSerializableMap : SerializationMap
364 public XmlSerializableMap (Type type, QName qname, KnownTypeCollection knownTypes)
365 : base (type, qname, knownTypes)
369 public override void Serialize (object graph, XmlFormatterSerializer serializer)
371 IXmlSerializable ixs = graph as IXmlSerializable;
373 //FIXME: Throw what exception here?
374 throw new SerializationException ();
376 ixs.WriteXml (serializer.Writer);
380 internal class SharedContractMap : SerializationMap
382 public SharedContractMap (
383 Type type, QName qname, KnownTypeCollection knownTypes)
384 : base (type, qname, knownTypes)
386 Type baseType = type;
387 List <DataMemberInfo> members = new List <DataMemberInfo> ();
389 while (baseType != null) {
390 QName bqname = knownTypes.GetQName (baseType);
392 members = GetMembers (baseType, bqname, true);
393 Members.InsertRange (0, members);
396 baseType = baseType.BaseType;
399 // Members.Sort (delegate (
400 // DataMemberInfo d1, DataMemberInfo d2) {
401 // return d1.Order - d2.Order;
405 List<DataMemberInfo> GetMembers (Type type, QName qname, bool declared_only)
407 List<DataMemberInfo> data_members = new List<DataMemberInfo> ();
408 BindingFlags flags = AllInstanceFlags;
410 flags |= BindingFlags.DeclaredOnly;
412 foreach (PropertyInfo pi in type.GetProperties (flags)) {
413 DataMemberAttribute dma =
414 GetDataMemberAttribute (pi);
417 if (!pi.CanRead || !pi.CanWrite)
418 throw new InvalidDataContractException (String.Format (
419 "DataMember property {0} must have both getter and setter.", pi));
420 data_members.Add (CreateDataMemberInfo (dma, pi, pi.PropertyType));
423 foreach (FieldInfo fi in type.GetFields (flags)) {
424 DataMemberAttribute dma =
425 GetDataMemberAttribute (fi);
429 throw new InvalidDataContractException (String.Format (
430 "DataMember field {0} must not be read-only.", fi));
431 data_members.Add (CreateDataMemberInfo (dma, fi, fi.FieldType));
434 data_members.Sort (DataMemberInfo.DataMemberInfoComparer.Instance);
439 public override List<DataMemberInfo> GetMembers ()
441 return GetMembers (RuntimeType, XmlName, true);
445 internal class CollectionTypeMap : SerializationMap
450 public CollectionTypeMap (
451 Type type, Type elementType,
452 QName qname, KnownTypeCollection knownTypes)
453 : base (type, qname, knownTypes)
455 element_type = elementType;
456 element_qname = KnownTypes.GetQName (element_type);
459 public override void Serialize (object graph,
460 XmlFormatterSerializer serializer)
462 string ns = element_qname.Namespace;
463 if (ns == KnownTypeCollection.MSSimpleNamespace)
464 ns = KnownTypeCollection.MSArraysNamespace;
466 foreach (object o in (IEnumerable) graph) {
467 serializer.WriteStartElement (element_qname.Name, XmlName.Namespace, ns);
468 serializer.Serialize (element_type, o);
469 serializer.WriteEndElement ();
473 public override object DeserializeContent(XmlReader reader, XmlFormatterDeserializer deserializer)
476 if (RuntimeType.IsArray)
477 instance = new ArrayList ();
479 instance = Activator.CreateInstance (RuntimeType, true);
480 int depth = reader.NodeType == XmlNodeType.None ? reader.Depth : reader.Depth - 1;
481 while (reader.NodeType == XmlNodeType.Element && reader.Depth > depth) {
482 object elem = deserializer.Deserialize (element_type, reader);
483 if (instance is IList)
484 ((IList)instance).Add (elem);
486 throw new NotImplementedException (String.Format ("Type {0} is not supported", RuntimeType));
488 if (RuntimeType.IsArray)
489 return ((ArrayList)instance).ToArray (element_type);
493 public override List<DataMemberInfo> GetMembers ()
495 //Shouldn't come here at all!
496 throw new NotImplementedException ();
500 public override XmlSchemaType GetSchemaType (XmlSchemaSet schemas, Dictionary<QName, XmlSchemaType> generated_schema_types)
502 if (generated_schema_types.ContainsKey (XmlName))
505 if (generated_schema_types.ContainsKey (XmlName))
506 return generated_schema_types [XmlName];
508 QName element_qname = GetQualifiedName (element_type);
510 XmlSchemaComplexType complex_type = new XmlSchemaComplexType ();
511 complex_type.Name = XmlName.Name;
513 XmlSchemaSequence sequence = new XmlSchemaSequence ();
514 XmlSchemaElement element = new XmlSchemaElement ();
516 element.MinOccurs = 0;
517 element.MaxOccursString = "unbounded";
518 element.Name = element_qname.Name;
520 KnownTypes.Add (element_type);
521 SerializationMap map = KnownTypes.FindUserMap (element_type);
522 if (map != null) {// non-primitive type
523 map.GetSchemaType (schemas, generated_schema_types);
524 element.IsNillable = true;
527 element.SchemaTypeName = element_qname;
529 sequence.Items.Add (element);
530 complex_type.Particle = sequence;
532 XmlSchema schema = GetSchema (schemas, XmlName.Namespace);
533 schema.Items.Add (complex_type);
534 schema.Items.Add (GetSchemaElement (XmlName, complex_type));
535 schemas.Reprocess (schema);
537 generated_schema_types [XmlName] = complex_type;
544 internal class SharedTypeMap : SerializationMap
546 public SharedTypeMap (
547 Type type, QName qname, KnownTypeCollection knownTypes)
548 : base (type, qname, knownTypes)
550 Members = GetMembers (type, XmlName, false);
553 List<DataMemberInfo> GetMembers (Type type, QName qname, bool declared_only)
555 List<DataMemberInfo> data_members = new List<DataMemberInfo> ();
557 BindingFlags flags = AllInstanceFlags;
559 flags |= BindingFlags.DeclaredOnly;
561 foreach (FieldInfo fi in type.GetFields (flags)) {
562 if (fi.GetCustomAttributes (
563 typeof (NonSerializedAttribute),
568 throw new InvalidDataContractException (String.Format ("DataMember field {0} must not be read-only.", fi));
569 DataMemberAttribute dma = new DataMemberAttribute ();
571 data_members.Add (CreateDataMemberInfo (dma, fi, fi.FieldType));
577 public override List<DataMemberInfo> GetMembers ()
579 return GetMembers (RuntimeType, XmlName, true);
583 internal class EnumMap : SerializationMap
585 List<EnumMemberInfo> enum_members;
588 Type type, QName qname, KnownTypeCollection knownTypes)
589 : base (type, qname, knownTypes)
592 object [] atts = RuntimeType.GetCustomAttributes (
593 typeof (DataContractAttribute), false);
594 if (atts.Length != 0)
597 enum_members = new List<EnumMemberInfo> ();
598 BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static;
600 foreach (FieldInfo fi in RuntimeType.GetFields (flags)) {
601 string name = fi.Name;
603 EnumMemberAttribute ema =
604 GetEnumMemberAttribute (fi);
608 if (ema.Value != null)
612 enum_members.Add (new EnumMemberInfo (name, fi.GetValue (null)));
616 private EnumMemberAttribute GetEnumMemberAttribute (
619 object [] atts = mi.GetCustomAttributes (
620 typeof (EnumMemberAttribute), false);
621 if (atts.Length == 0)
623 return (EnumMemberAttribute) atts [0];
627 public override XmlSchemaType GetSchemaType (XmlSchemaSet schemas, Dictionary<QName, XmlSchemaType> generated_schema_types)
629 if (generated_schema_types.ContainsKey (XmlName))
630 return generated_schema_types [XmlName];
632 XmlSchemaSimpleType simpleType = new XmlSchemaSimpleType ();
633 simpleType.Name = XmlName.Name;
635 XmlSchemaSimpleTypeRestriction simpleRestriction = new XmlSchemaSimpleTypeRestriction ();
636 simpleType.Content = simpleRestriction;
637 simpleRestriction.BaseTypeName = new XmlQualifiedName ("string", XmlSchema.Namespace);
639 foreach (EnumMemberInfo emi in enum_members) {
640 XmlSchemaEnumerationFacet e = new XmlSchemaEnumerationFacet ();
641 e.Value = emi.XmlName;
642 simpleRestriction.Facets.Add (e);
645 generated_schema_types [XmlName] = simpleType;
647 XmlSchema schema = GetSchema (schemas, XmlName.Namespace);
648 XmlSchemaElement element = GetSchemaElement (XmlName, simpleType);
649 element.IsNillable = true;
651 schema.Items.Add (simpleType);
652 schema.Items.Add (element);
658 public override void Serialize (object graph,
659 XmlFormatterSerializer serializer)
661 foreach (EnumMemberInfo emi in enum_members) {
662 if (Enum.Equals (emi.Value, graph)) {
663 serializer.Writer.WriteString (emi.XmlName);
668 throw new SerializationException (String.Format (
669 "Enum value '{0}' is invalid for type '{1}' and cannot be serialized.", graph, RuntimeType));
672 public override object DeserializeContent (XmlReader reader,
673 XmlFormatterDeserializer deserializer)
675 string value = reader.NodeType != XmlNodeType.Text ? String.Empty : reader.ReadContentAsString ();
677 if (value != String.Empty) {
678 foreach (EnumMemberInfo emi in enum_members)
679 if (emi.XmlName == value)
683 throw new SerializationException (String.Format (
684 "Enum value '{0}' is invalid for type '{1}' and cannot be deserialized.", value, RuntimeType));
688 internal struct EnumMemberInfo
690 public readonly string XmlName;
691 public readonly object Value;
693 public EnumMemberInfo (string name, object value)
700 internal class DataMemberInfo //: KeyValuePair<int, MemberInfo>
702 public readonly int Order;
703 public readonly bool IsRequired;
704 public readonly string XmlName;
705 public readonly MemberInfo Member;
706 public readonly string XmlNamespace;
707 public readonly string XmlRootNamespace;
708 public readonly Type MemberType;
710 public DataMemberInfo (MemberInfo member, DataMemberAttribute dma, string rootNamespce, string ns)
713 throw new ArgumentNullException ("dma");
716 IsRequired = dma.IsRequired;
717 XmlName = dma.Name != null ? dma.Name : member.Name;
719 XmlRootNamespace = rootNamespce;
720 if (Member is FieldInfo)
721 MemberType = ((FieldInfo) Member).FieldType;
723 MemberType = ((PropertyInfo) Member).PropertyType;
726 public class DataMemberInfoComparer : IComparer<DataMemberInfo>
727 , IComparer // see bug #76361
729 public static readonly DataMemberInfoComparer Instance
730 = new DataMemberInfoComparer ();
732 private DataMemberInfoComparer () {}
734 public int Compare (object o1, object o2)
736 return Compare ((DataMemberInfo) o1,
737 (DataMemberInfo) o2);
740 public int Compare (DataMemberInfo d1, DataMemberInfo d2)
742 if (d1.Order == -1 || d2.Order == -1)
743 return String.CompareOrdinal (d1.XmlName, d2.XmlName);
745 return d1.Order - d2.Order;