Look for KnownTypeAttributes on known types (recursively). Fixed bug #695203.
[mono.git] / mcs / class / System.Runtime.Serialization / System.Runtime.Serialization / DataContractSerializer.cs
1 //
2 // DataContractSerializer.cs
3 //
4 // Author:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Copyright (C) 2005-2007 Novell, Inc.  http://www.novell.com
8 //
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:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
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.
27 //
28 #if NET_2_0
29 using System;
30 using System.Collections;
31 using System.Collections.Generic;
32 using System.Collections.ObjectModel;
33 using System.IO;
34 using System.Reflection;
35 using System.Runtime.Serialization.Formatters.Binary;
36 using System.Xml;
37 using System.Xml.Schema;
38
39 using QName = System.Xml.XmlQualifiedName;
40
41 namespace System.Runtime.Serialization
42 {
43         public sealed class DataContractSerializer : XmlObjectSerializer
44         {
45                 const string xmlns = "http://www.w3.org/2000/xmlns/";
46
47                 Type type;
48                 bool ignore_ext, preserve_refs;
49
50                 // This is only for compatible mode.
51                 StreamingContext context;
52                 ReadOnlyCollection<Type> returned_known_types;
53                 KnownTypeCollection known_types;
54                 IDataContractSurrogate surrogate;
55                 DataContractResolver resolver, default_resolver;
56
57                 int max_items = 0x10000; // FIXME: could be from config.
58
59                 bool names_filled;
60                 XmlDictionaryString root_name, root_ns;
61
62                 public DataContractSerializer (Type type)
63                         : this (type, Type.EmptyTypes)
64                 {
65                         // nothing to do here.
66                 }
67
68                 public DataContractSerializer (Type type,
69                         IEnumerable<Type> knownTypes)
70                 {
71                         if (type == null)
72                                 throw new ArgumentNullException ("type");
73                         this.type = type;
74                         PopulateTypes (knownTypes);
75                         known_types.Add (type);
76                         QName qname = known_types.GetQName (type);
77
78                         FillDictionaryString (qname.Name, qname.Namespace);
79                         
80                 }
81
82                 public DataContractSerializer (Type type, string rootName,
83                         string rootNamespace)
84                         : this (type, rootName, rootNamespace, Type.EmptyTypes)
85                 {
86                         // nothing to do here.
87                 }
88
89                 public DataContractSerializer (Type type,
90                         XmlDictionaryString rootName,
91                         XmlDictionaryString rootNamespace)
92                         : this (type, rootName, rootNamespace, Type.EmptyTypes)
93                 {
94                         // nothing to do here.
95                 }
96
97                 public DataContractSerializer (Type type, string rootName,
98                         string rootNamespace, IEnumerable<Type> knownTypes)
99                 {
100                         if (type == null)
101                                 throw new ArgumentNullException ("type");
102                         if (rootName == null)
103                                 throw new ArgumentNullException ("rootName");
104                         if (rootNamespace == null)
105                                 throw new ArgumentNullException ("rootNamespace");
106                         this.type = type;
107                         PopulateTypes (knownTypes);
108                         FillDictionaryString (rootName, rootNamespace);
109                 }
110
111                 public DataContractSerializer (Type type,
112                         XmlDictionaryString rootName,
113                         XmlDictionaryString rootNamespace,
114                         IEnumerable<Type> knownTypes)
115                 {
116                         if (type == null)
117                                 throw new ArgumentNullException ("type");
118                         if (rootName == null)
119                                 throw new ArgumentNullException ("rootName");
120                         if (rootNamespace == null)
121                                 throw new ArgumentNullException ("rootNamespace");
122                         this.type = type;
123                         PopulateTypes (knownTypes);
124                         root_name = rootName;
125                         root_ns = rootNamespace;
126                 }
127
128                 public DataContractSerializer (Type type,
129                         IEnumerable<Type> knownTypes,
130                         int maxObjectsInGraph,
131                         bool ignoreExtensionDataObject,
132                         bool preserveObjectReferences,
133                         IDataContractSurrogate dataContractSurrogate)
134                         : this (type, knownTypes)
135                 {
136                         Initialize (maxObjectsInGraph,
137                                 ignoreExtensionDataObject,
138                                 preserveObjectReferences,
139                                 dataContractSurrogate);
140                 }
141
142                 public DataContractSerializer (Type type,
143                         string rootName,
144                         string rootNamespace,
145                         IEnumerable<Type> knownTypes,
146                         int maxObjectsInGraph,
147                         bool ignoreExtensionDataObject,
148                         bool preserveObjectReferences,
149                         IDataContractSurrogate dataContractSurrogate)
150                         : this (type, rootName, rootNamespace, knownTypes)
151                 {
152                         Initialize (maxObjectsInGraph,
153                                 ignoreExtensionDataObject,
154                                 preserveObjectReferences,
155                                 dataContractSurrogate);
156                 }
157
158                 public DataContractSerializer (Type type,
159                         XmlDictionaryString rootName,
160                         XmlDictionaryString rootNamespace,
161                         IEnumerable<Type> knownTypes,
162                         int maxObjectsInGraph,
163                         bool ignoreExtensionDataObject,
164                         bool preserveObjectReferences,
165                         IDataContractSurrogate dataContractSurrogate)
166                         : this (type, rootName, rootNamespace, knownTypes)
167                 {
168                         Initialize (maxObjectsInGraph,
169                                 ignoreExtensionDataObject,
170                                 preserveObjectReferences,
171                                 dataContractSurrogate);
172                 }
173
174 #if NET_4_0
175                 public DataContractSerializer (Type type,
176                         IEnumerable<Type> knownTypes,
177                         int maxObjectsInGraph,
178                         bool ignoreExtensionDataObject,
179                         bool preserveObjectReferences,
180                         IDataContractSurrogate dataContractSurrogate,
181                         DataContractResolver dataContractResolver)
182                         : this (type, knownTypes, maxObjectsInGraph, ignoreExtensionDataObject, preserveObjectReferences, dataContractSurrogate)
183                 {
184                         DataContractResolver = dataContractResolver;
185                 }
186
187                 public DataContractSerializer (Type type,
188                         string rootName,
189                         string rootNamespace,
190                         IEnumerable<Type> knownTypes,
191                         int maxObjectsInGraph,
192                         bool ignoreExtensionDataObject,
193                         bool preserveObjectReferences,
194                         IDataContractSurrogate dataContractSurrogate,
195                         DataContractResolver dataContractResolver)
196                         : this (type, rootName, rootNamespace, knownTypes, maxObjectsInGraph, ignoreExtensionDataObject, preserveObjectReferences, dataContractSurrogate)
197                 {
198                         DataContractResolver = dataContractResolver;
199                 }
200
201                 public DataContractSerializer (Type type,
202                         XmlDictionaryString rootName,
203                         XmlDictionaryString rootNamespace,
204                         IEnumerable<Type> knownTypes,
205                         int maxObjectsInGraph,
206                         bool ignoreExtensionDataObject,
207                         bool preserveObjectReferences,
208                         IDataContractSurrogate dataContractSurrogate,
209                         DataContractResolver dataContractResolver)
210                         : this (type, rootName, rootNamespace, knownTypes, maxObjectsInGraph, ignoreExtensionDataObject, preserveObjectReferences, dataContractSurrogate)
211                 {
212                         DataContractResolver = dataContractResolver;
213                 }
214 #endif
215
216                 void PopulateTypes (IEnumerable<Type> knownTypes)
217                 {
218                         if (known_types == null)
219                                 known_types= new KnownTypeCollection ();
220
221                         if (knownTypes != null) {
222                                 foreach (Type t in knownTypes)
223                                         known_types.Add (t);
224                         }
225
226                         RegisterTypeAsKnown (type);
227                 }
228
229                 void RegisterTypeAsKnown (Type type)
230                 {
231                         if (known_types.Contains (type))
232                                 return;
233
234                         Type elementType = type;
235                         if (type.HasElementType)
236                                 elementType = type.GetElementType ();
237
238                         known_types.Add (elementType);
239
240                         /* Get all KnownTypeAttribute-s, including inherited ones */
241                         object [] attrs = elementType.GetCustomAttributes (typeof (KnownTypeAttribute), true);
242                         for (int i = 0; i < attrs.Length; i ++) {
243                                 KnownTypeAttribute kt = (KnownTypeAttribute) attrs [i];
244                                 foreach (var t in kt.GetTypes (elementType))
245                                         RegisterTypeAsKnown (t);
246                         }
247                 }
248
249                 void FillDictionaryString (string name, string ns)
250                 {
251                         XmlDictionary d = new XmlDictionary ();
252                         root_name = d.Add (name);
253                         root_ns = d.Add (ns);
254                         names_filled = true;
255                 }
256
257                 void Initialize (
258                         int maxObjectsInGraph,
259                         bool ignoreExtensionDataObject,
260                         bool preserveObjectReferences,
261                         IDataContractSurrogate dataContractSurrogate)
262                 {
263                         if (maxObjectsInGraph < 0)
264                                 throw new ArgumentOutOfRangeException ("maxObjectsInGraph must not be negative.");
265                         max_items = maxObjectsInGraph;
266                         ignore_ext = ignoreExtensionDataObject;
267                         preserve_refs = preserveObjectReferences;
268                         surrogate = dataContractSurrogate;
269                 }
270
271 #if NET_4_0
272                 public
273 #else
274                 internal
275 #endif
276                 DataContractResolver DataContractResolver {
277                         get { return resolver; }
278                         private set {
279                                 resolver = value;
280                                 default_resolver = default_resolver ?? new DefaultDataContractResolver (this);
281                         }
282                 }
283
284                 public bool IgnoreExtensionDataObject {
285                         get { return ignore_ext; }
286                 }
287
288                 public ReadOnlyCollection<Type> KnownTypes {
289                         get {
290                                 if (returned_known_types == null)
291                                         returned_known_types = new ReadOnlyCollection<Type> (known_types);
292                                 return returned_known_types;
293                         }
294                 }
295
296                 internal KnownTypeCollection InternalKnownTypes {
297                         get { return known_types; }
298                 }
299
300                 public IDataContractSurrogate DataContractSurrogate {
301                         get { return surrogate; }
302                 }
303
304                 public int MaxItemsInObjectGraph {
305                         get { return max_items; }
306                 }
307
308                 public bool PreserveObjectReferences {
309                         get { return preserve_refs; }
310                 }
311
312                 public override bool IsStartObject (XmlDictionaryReader reader)
313                 {
314                         if (reader == null)
315                                 throw new ArgumentNullException ("reader");
316                         reader.MoveToContent ();
317                         return reader.IsStartElement (root_name, root_ns);
318                 }
319
320                 // SP1
321                 public override bool IsStartObject (XmlReader reader)
322                 {
323                         return IsStartObject (XmlDictionaryReader.CreateDictionaryReader (reader));
324                 }
325
326                 // SP1
327                 public override object ReadObject (XmlReader reader)
328                 {
329                         return ReadObject (XmlDictionaryReader.CreateDictionaryReader (reader));
330                 }
331
332                 public override object ReadObject (XmlReader reader, bool verifyObjectName)
333                 {
334                         return ReadObject (XmlDictionaryReader.CreateDictionaryReader (reader), verifyObjectName);
335                 }
336
337                 public override object ReadObject (XmlDictionaryReader reader, bool verifyObjectName)
338                 {
339                         int startTypeCount = known_types.Count;
340                         known_types.Add (type);
341
342                         bool isEmpty = reader.IsEmptyElement;
343
344                         object ret = XmlFormatterDeserializer.Deserialize (reader, type,
345                                 known_types, surrogate, DataContractResolver, default_resolver, root_name.Value, root_ns.Value, verifyObjectName);
346
347                         // remove temporarily-added known types for
348                         // rootType and object graph type.
349                         while (known_types.Count > startTypeCount)
350                                 known_types.RemoveAt (startTypeCount);
351
352                         return ret;
353                 }
354
355 #if NET_4_0
356                 public object ReadObject (XmlDictionaryReader reader, bool verifyObjectName, DataContractResolver resolver)
357                 {
358                         var bak = DataContractResolver;
359                         try {
360                                 DataContractResolver = resolver;
361                                 return ReadObject (reader, verifyObjectName);
362                         } finally {
363                                 DataContractResolver = bak;
364                         }
365                 }
366 #endif
367
368                 private void ReadRootStartElement (XmlReader reader, Type type)
369                 {
370                         SerializationMap map =
371                                 known_types.FindUserMap (type);
372                         QName name = map != null ? map.XmlName :
373                                 KnownTypeCollection.GetPredefinedTypeName (type);
374                         reader.MoveToContent ();
375                         reader.ReadStartElement (name.Name, name.Namespace);
376                         // FIXME: could there be any attributes to handle here?
377                         reader.Read ();
378                 }
379
380                 // SP1
381                 public override void WriteObject (XmlWriter writer, object graph)
382                 {
383                         XmlDictionaryWriter w = XmlDictionaryWriter.CreateDictionaryWriter (writer);
384                         WriteObject (w, graph);
385                 }
386
387 #if NET_4_0
388                 public void WriteObject (XmlDictionaryWriter writer, object graph, DataContractResolver resolver)
389                 {
390                         var bak = DataContractResolver;
391                         try {
392                                 DataContractResolver = resolver;
393                                 WriteObject (writer, graph);
394                         } finally {
395                                 DataContractResolver = bak;
396                         }
397                 }
398 #endif
399
400                 [MonoTODO ("use DataContractSurrogate")]
401                 /*
402                         when writeContentOnly is true, then the input XmlWriter
403                         must be at element state. This is to write possible
404                         xsi:nil.
405
406                         rootType determines the top-level element QName (thus
407                         it is ignored when writeContentOnly is true).
408
409                         preserveObjectReferences indicates that whether the
410                         output should contain ms:Id or not.
411                         (http://schemas.microsoft.com/2003/10/Serialization/)
412                 */
413                 public override void WriteObjectContent (XmlDictionaryWriter writer, object graph)
414                 {
415                         if (graph == null)
416                                 return;
417
418                         int startTypeCount = known_types.Count;
419
420                         XmlFormatterSerializer.Serialize (writer, graph,
421                                 type, known_types,
422                                 ignore_ext, max_items, root_ns.Value, preserve_refs, DataContractResolver, default_resolver);
423
424                         // remove temporarily-added known types for
425                         // rootType and object graph type.
426                         while (known_types.Count > startTypeCount)
427                                 known_types.RemoveAt (startTypeCount);
428                 }
429
430                 public override void WriteObjectContent (XmlWriter writer, object graph)
431                 {
432                         XmlDictionaryWriter w = XmlDictionaryWriter.CreateDictionaryWriter (writer);
433                         WriteObjectContent (w, graph);
434                 }
435
436                 // SP1
437                 public override void WriteStartObject (
438                         XmlWriter writer, object graph)
439                 {
440                         WriteStartObject (XmlDictionaryWriter.CreateDictionaryWriter (writer), graph);
441                 }
442
443                 public override void WriteStartObject (
444                         XmlDictionaryWriter writer, object graph)
445                 {
446                         Type rootType = type;
447                         
448                         if (root_name.Value == "")
449                                 throw new InvalidDataContractException ("Type '" + type.ToString () +
450                                         "' cannot have a DataContract attribute Name set to null or empty string.");
451
452
453                         if (graph == null) {
454                                 if (names_filled)
455                                         writer.WriteStartElement (root_name.Value, root_ns.Value);
456                                 else
457                                         writer.WriteStartElement (root_name, root_ns);
458                                 writer.WriteAttributeString ("i", "nil", XmlSchema.InstanceNamespace, "true");
459                                 return;
460                         }
461
462                         QName rootQName = null;
463                         XmlDictionaryString name, ns;
464                         if (DataContractResolver != null && DataContractResolver.TryResolveType (graph.GetType (), type, default_resolver, out name, out ns))
465                                 rootQName = new QName (name.Value, ns.Value);
466
467                         // It is error unless 1) TypeResolver resolved the type name, 2) the object is the exact type, 3) the object is known or 4) the type is primitive.
468
469                         if (rootQName == null &&
470                             graph.GetType () != type &&
471                             IsUnknownType (graph.GetType ()))
472                                 throw new SerializationException (String.Format ("Type '{0}' is unexpected. The type should either be registered as a known type, or DataContractResolver should be used.", graph.GetType ()));
473
474                         QName instName = rootQName;
475                         rootQName = rootQName ?? known_types.GetQName (rootType);
476                         QName graph_qname = known_types.GetQName (graph.GetType ());
477
478                         known_types.Add (graph.GetType ());
479
480                         if (names_filled)
481                                 writer.WriteStartElement (root_name.Value, root_ns.Value);
482                         else
483                                 writer.WriteStartElement (root_name, root_ns);
484
485                         if (rootQName != graph_qname || rootQName.Namespace != KnownTypeCollection.MSSimpleNamespace && !rootType.IsEnum)
486                                 //FIXME: Hack, when should the "i:type" be written?
487                                 //Not used in case of enums
488                                 writer.WriteXmlnsAttribute ("i", XmlSchema.InstanceNamespace);
489
490                         if (root_ns.Value != rootQName.Namespace)
491                                 if (rootQName.Namespace != KnownTypeCollection.MSSimpleNamespace)
492                                         writer.WriteXmlnsAttribute (null, rootQName.Namespace);
493
494                         if (rootQName == graph_qname)
495                                 return;
496
497                         /* Different names */
498                         known_types.Add (rootType);
499                         
500                         instName = instName ?? KnownTypeCollection.GetPredefinedTypeName (graph.GetType ());
501                         if (instName == QName.Empty)
502                                 /* Not a primitive type */
503                                 instName = graph_qname;
504                         else
505                                 /* FIXME: Hack, .. see test WriteObject7 () */
506                                 instName = new QName (instName.Name, XmlSchema.Namespace);
507
508 /* // disabled as it now generates extraneous i:type output.
509                         // output xsi:type as rootType is not equivalent to the graph's type.
510                         writer.WriteStartAttribute ("i", "type", XmlSchema.InstanceNamespace);
511                         writer.WriteQualifiedName (instName.Name, instName.Namespace);
512                         writer.WriteEndAttribute ();
513 */
514                 }
515                 
516                 bool IsUnknownType (Type type)
517                 {
518                         if (known_types.Contains (type) ||
519                             KnownTypeCollection.GetPrimitiveTypeName (type) != QName.Empty)
520                                 return false;
521                         if (type.IsArray)
522                                 return IsUnknownType (type.GetElementType ());
523                         return true;
524                 }
525
526                 public override void WriteEndObject (XmlDictionaryWriter writer)
527                 {
528                         writer.WriteEndElement ();
529                 }
530
531                 // SP1
532                 public override void WriteEndObject (XmlWriter writer)
533                 {
534                         WriteEndObject (XmlDictionaryWriter.CreateDictionaryWriter (writer));
535                 }
536         }
537 }
538 #endif