2010-03-12 Jb Evain <jbevain@novell.com>
[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> known_runtime_types;
53                 KnownTypeCollection known_types;
54                 IDataContractSurrogate surrogate;
55
56                 int max_items = 0x10000; // FIXME: could be from config.
57
58                 bool names_filled;
59                 XmlDictionaryString root_name, root_ns;
60
61                 public DataContractSerializer (Type type)
62                         : this (type, Type.EmptyTypes)
63                 {
64                         // nothing to do here.
65                 }
66
67                 public DataContractSerializer (Type type,
68                         IEnumerable<Type> knownTypes)
69                 {
70                         if (type == null)
71                                 throw new ArgumentNullException ("type");
72                         this.type = type;
73                         known_types = new KnownTypeCollection ();
74                         PopulateTypes (knownTypes);
75                         known_types.TryRegister (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                 void PopulateTypes (IEnumerable<Type> knownTypes)
175                 {
176                         if (known_types == null)
177                                 known_types= new KnownTypeCollection ();
178
179                         if (knownTypes != null) {
180                                 foreach (Type t in knownTypes)
181                                         known_types.TryRegister (t);
182                         }
183
184                         Type elementType = type;
185                         if (type.HasElementType)
186                                 elementType = type.GetElementType ();
187
188                         /* Get all KnownTypeAttribute-s, including inherited ones */
189                         object [] attrs = elementType.GetCustomAttributes (typeof (KnownTypeAttribute), true);
190                         for (int i = 0; i < attrs.Length; i ++) {
191                                 KnownTypeAttribute kt = (KnownTypeAttribute) attrs [i];
192                                 known_types.TryRegister (kt.Type);
193                         }
194                 }
195
196                 void FillDictionaryString (string name, string ns)
197                 {
198                         XmlDictionary d = new XmlDictionary ();
199                         root_name = d.Add (name);
200                         root_ns = d.Add (ns);
201                         names_filled = true;
202                 }
203
204                 void Initialize (
205                         int maxObjectsInGraph,
206                         bool ignoreExtensionDataObject,
207                         bool preserveObjectReferences,
208                         IDataContractSurrogate dataContractSurrogate)
209                 {
210                         if (maxObjectsInGraph < 0)
211                                 throw new ArgumentOutOfRangeException ("maxObjectsInGraph must not be negative.");
212                         max_items = maxObjectsInGraph;
213                         ignore_ext = ignoreExtensionDataObject;
214                         preserve_refs = preserveObjectReferences;
215                         surrogate = dataContractSurrogate;
216
217                         PopulateTypes (Type.EmptyTypes);
218                 }
219
220                 public bool IgnoreExtensionDataObject {
221                         get { return ignore_ext; }
222                 }
223
224                 public ReadOnlyCollection<Type> KnownTypes {
225                         get { return known_runtime_types; }
226                 }
227
228                 public IDataContractSurrogate DataContractSurrogate {
229                         get { return surrogate; }
230                 }
231
232                 public int MaxItemsInObjectGraph {
233                         get { return max_items; }
234                 }
235
236                 public bool PreserveObjectReferences {
237                         get { return preserve_refs; }
238                 }
239
240                 [MonoTODO]
241                 public override bool IsStartObject (XmlDictionaryReader reader)
242                 {
243                         throw new NotImplementedException ();
244                 }
245
246                 // SP1
247                 public override bool IsStartObject (XmlReader reader)
248                 {
249                         return IsStartObject (XmlDictionaryReader.CreateDictionaryReader (reader));
250                 }
251
252                 // SP1
253                 public override object ReadObject (XmlReader reader)
254                 {
255                         return ReadObject (XmlDictionaryReader.CreateDictionaryReader (reader));
256                 }
257
258                 public override object ReadObject (XmlReader reader, bool verifyObjectName)
259                 {
260                         return ReadObject (XmlDictionaryReader.CreateDictionaryReader (reader), verifyObjectName);
261                 }
262
263                 [MonoTODO]
264                 public override object ReadObject (XmlDictionaryReader reader, bool verifyObjectName)
265                 {
266                         int startTypeCount = known_types.Count;
267                         known_types.Add (type);
268
269                         bool isEmpty = reader.IsEmptyElement;
270
271                         object ret = XmlFormatterDeserializer.Deserialize (reader, type,
272                                 known_types, surrogate, root_name.Value, root_ns.Value, verifyObjectName);
273
274                         // remove temporarily-added known types for
275                         // rootType and object graph type.
276                         while (known_types.Count > startTypeCount)
277                                 known_types.RemoveAt (startTypeCount);
278
279                         return ret;
280                 }
281
282                 private void ReadRootStartElement (XmlReader reader, Type type)
283                 {
284                         SerializationMap map =
285                                 known_types.FindUserMap (type);
286                         QName name = map != null ? map.XmlName :
287                                 KnownTypeCollection.GetPredefinedTypeName (type);
288                         reader.MoveToContent ();
289                         reader.ReadStartElement (name.Name, name.Namespace);
290                         // FIXME: could there be any attributes to handle here?
291                         reader.Read ();
292                 }
293
294                 // SP1
295                 public override void WriteObject (XmlWriter writer, object graph)
296                 {
297                         XmlDictionaryWriter w = XmlDictionaryWriter.CreateDictionaryWriter (writer);
298                         WriteObject (w, graph);
299                 }
300
301                 [MonoTODO ("support arrays; support Serializable; support SharedType; use DataContractSurrogate")]
302                 /*
303                         when writeContentOnly is true, then the input XmlWriter
304                         must be at element state. This is to write possible
305                         xsi:nil.
306
307                         rootType determines the top-level element QName (thus
308                         it is ignored when writeContentOnly is true).
309
310                         preserveObjectReferences indicates that whether the
311                         output should contain ms:Id or not.
312                         (http://schemas.microsoft.com/2003/10/Serialization/)
313                 */
314                 public override void WriteObjectContent (XmlDictionaryWriter writer, object graph)
315                 {
316                         if (graph == null)
317                                 return;
318
319                         int startTypeCount = known_types.Count;
320
321                         XmlFormatterSerializer.Serialize (writer, graph,
322                                 known_types,
323                                 ignore_ext, max_items, root_ns.Value);
324
325                         // remove temporarily-added known types for
326                         // rootType and object graph type.
327                         while (known_types.Count > startTypeCount)
328                                 known_types.RemoveAt (startTypeCount);
329                 }
330
331                 public override void WriteObjectContent (XmlWriter writer, object graph)
332                 {
333                         XmlDictionaryWriter w = XmlDictionaryWriter.CreateDictionaryWriter (writer);
334                         WriteObjectContent (w, graph);
335                 }
336
337                 // SP1
338                 public override void WriteStartObject (
339                         XmlWriter writer, object graph)
340                 {
341                         WriteStartObject (XmlDictionaryWriter.CreateDictionaryWriter (writer), graph);
342                 }
343
344                 public override void WriteStartObject (
345                         XmlDictionaryWriter writer, object graph)
346                 {
347                         Type rootType = type;
348                         
349                         if (root_name.Value == "")
350                                 throw new InvalidDataContractException ("Type '" + type.ToString () +
351                                         "' cannot have a DataContract attribute Name set to null or empty string.");
352
353
354                         if (graph == null) {
355                                 if (names_filled)
356                                         writer.WriteStartElement (root_name.Value, root_ns.Value);
357                                 else
358                                         writer.WriteStartElement (root_name, root_ns);
359                                 writer.WriteAttributeString ("i", "nil", XmlSchema.InstanceNamespace, "true");
360                                 return;
361                         }
362
363                         QName instName = null;
364                         QName root_qname = known_types.GetQName (rootType);
365                         QName graph_qname = known_types.GetQName (graph.GetType ());
366
367                         known_types.Add (graph.GetType ());
368
369                         if (names_filled)
370                                 writer.WriteStartElement (root_name.Value, root_ns.Value);
371                         else
372                                 writer.WriteStartElement (root_name, root_ns);
373                         if (root_ns.Value != root_qname.Namespace)
374                                 if (root_qname.Namespace != KnownTypeCollection.MSSimpleNamespace)
375                                         writer.WriteXmlnsAttribute (null, root_qname.Namespace);
376
377                         if (root_qname == graph_qname) {
378                                 if (root_qname.Namespace != KnownTypeCollection.MSSimpleNamespace &&
379                                         !rootType.IsEnum)
380                                         //FIXME: Hack, when should the "i:type" be written?
381                                         //Not used in case of enums
382                                         writer.WriteXmlnsAttribute ("i", XmlSchema.InstanceNamespace);
383
384                                 return;
385                         }
386
387                         /* Different names */
388                         known_types.Add (rootType);
389                         
390                         instName = KnownTypeCollection.GetPredefinedTypeName (graph.GetType ());
391                         if (instName == QName.Empty)
392                                 /* Not a primitive type */
393                                 instName = graph_qname;
394                         else
395                                 /* FIXME: Hack, .. see test WriteObject7 () */
396                                 instName = new QName (instName.Name, XmlSchema.Namespace);
397
398                         // output xsi:type as rootType is not equivalent to the graph's type.
399                         writer.WriteStartAttribute ("i", "type", XmlSchema.InstanceNamespace);
400                         writer.WriteQualifiedName (instName.Name, instName.Namespace);
401                         writer.WriteEndAttribute ();
402                 }
403
404                 public override void WriteEndObject (XmlDictionaryWriter writer)
405                 {
406                         writer.WriteEndElement ();
407                 }
408
409                 // SP1
410                 public override void WriteEndObject (XmlWriter writer)
411                 {
412                         WriteEndObject (XmlDictionaryWriter.CreateDictionaryWriter (writer));
413                 }
414         }
415 }
416 #endif