Merge pull request #409 from Alkarex/patch-1
[mono.git] / mcs / class / System.Runtime.Serialization / System.Runtime.Serialization / XmlFormatterDeserializer.cs
1 //
2 // XmlFormatterDeserializer.cs
3 //
4 // Author:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Copyright (C) 2005 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.Generic;
31 using System.IO;
32 using System.Linq;
33 using System.Reflection;
34 using System.Runtime.Serialization.Formatters.Binary;
35 using System.Xml;
36 using System.Xml.Schema;
37
38 using QName = System.Xml.XmlQualifiedName;
39
40 namespace System.Runtime.Serialization
41 {
42         internal class XmlFormatterDeserializer
43         {
44                 KnownTypeCollection types;
45                 IDataContractSurrogate surrogate;
46                 DataContractResolver resolver, default_resolver; // new in 4.0.
47                 // 3.5 SP1 supports deserialization by reference (id->obj).
48                 // Though unlike XmlSerializer, it does not support forward-
49                 // reference resolution i.e. a referenced object must appear
50                 // before any references to it.
51                 Dictionary<string,object> references = new Dictionary<string,object> ();
52                 Dictionary<QName,Type> resolved_qnames = new Dictionary<QName,Type> ();
53
54                 public static object Deserialize (XmlReader reader, Type declaredType,
55                         KnownTypeCollection knownTypes, IDataContractSurrogate surrogate, DataContractResolver resolver, DataContractResolver defaultResolver,
56                         string name, string ns, bool verifyObjectName)
57                 {
58                         reader.MoveToContent ();
59                         if (verifyObjectName)
60                                 if (reader.NodeType != XmlNodeType.Element ||
61                                     reader.LocalName != name ||
62                                     reader.NamespaceURI != ns)
63                                         throw new SerializationException (String.Format ("Expected element '{0}' in namespace '{1}', but found {2} node '{3}' in namespace '{4}'", name, ns, reader.NodeType, reader.LocalName, reader.NamespaceURI));
64 //                              Verify (knownTypes, declaredType, name, ns, reader);
65                         return new XmlFormatterDeserializer (knownTypes, surrogate, resolver, defaultResolver).Deserialize (declaredType, reader);
66                 }
67
68                 // Verify the top element name and namespace.
69                 private static void Verify (KnownTypeCollection knownTypes, Type type, string name, string Namespace, XmlReader reader)
70                 {
71                         QName graph_qname = new QName (reader.LocalName, reader.NamespaceURI);
72                         if (graph_qname.Name == name && graph_qname.Namespace == Namespace)
73                                 return;
74
75                         // <BClass .. i:type="EClass" >..</BClass>
76                         // Expecting type EClass : allowed
77                         // See test Serialize1b, and Serialize1c (for
78                         // negative cases)
79
80                         // Run through inheritance heirarchy .. 
81                         for (Type baseType = type; baseType != null; baseType = baseType.BaseType)
82                                 if (knownTypes.GetQName (baseType) == graph_qname)
83                                         return;
84
85                         QName typeQName = knownTypes.GetQName (type);
86                         throw new SerializationException (String.Format (
87                                 "Expecting element '{0}' from namespace '{1}'. Encountered 'Element' with name '{2}', namespace '{3}'",
88                                 typeQName.Name, typeQName.Namespace, graph_qname.Name, graph_qname.Namespace));
89                 }
90
91                 private XmlFormatterDeserializer (
92                         KnownTypeCollection knownTypes,
93                         IDataContractSurrogate surrogate,
94                         DataContractResolver resolver,
95                         DataContractResolver defaultResolver)
96                 {
97                         this.types = knownTypes;
98                         this.surrogate = surrogate;
99                         this.resolver = resolver;
100                         this.default_resolver = defaultResolver;
101                 }
102
103                 public Dictionary<string,object> References {
104                         get { return references; }
105                 }
106
107 #if !MOONLIGHT
108                 XmlDocument document;
109                 
110                 XmlDocument XmlDocument {
111                         get { return (document = document ?? new XmlDocument ()); }
112                 }
113 #endif
114
115                 // This method handles z:Ref, xsi:nil and primitive types, and then delegates to DeserializeByMap() for anything else.
116
117                 public object Deserialize (Type type, XmlReader reader)
118                 {
119 #if !MOONLIGHT
120                         if (type == typeof (XmlElement))
121                                 return XmlDocument.ReadNode (reader);
122                         else if (type == typeof (XmlNode [])) {
123                                 reader.ReadStartElement ();
124                                 var l = new List<XmlNode> ();
125                                 for(; !reader.EOF && reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ())
126                                         l.Add (XmlDocument.ReadNode (reader));
127                                 reader.ReadEndElement ();
128                                 return l.ToArray ();
129                         }
130 #endif
131                         QName graph_qname = null;
132                         
133                         if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>)) {
134                                 Type internal_type = type.GetGenericArguments () [0];
135                                 
136                                 if (types.FindUserMap(internal_type) != null) {
137                                         graph_qname = types.GetQName (internal_type);
138                                 }
139                         }
140                         
141                         if (graph_qname == null)
142                                 graph_qname = types.GetQName (type);
143                                 
144                         string itype = reader.GetAttribute ("type", XmlSchema.InstanceNamespace);
145                         if (itype != null) {
146                                 string [] parts = itype.Split (':');
147                                 if (parts.Length > 1)
148                                         graph_qname = new QName (parts [1], reader.LookupNamespace (reader.NameTable.Get (parts [0])));
149                                 else
150                                         graph_qname = new QName (itype, reader.LookupNamespace (String.Empty));
151                         }
152
153                         string label = reader.GetAttribute ("Ref", KnownTypeCollection.MSSimpleNamespace);
154                         if (label != null) {
155                                 object o;
156                                 if (!references.TryGetValue (label, out o))
157                                         throw new SerializationException (String.Format ("Deserialized object with reference Id '{0}' was not found", label));
158                                 reader.Skip ();
159                                 return o;
160                         }
161
162                         bool isNil = reader.GetAttribute ("nil", XmlSchema.InstanceNamespace) == "true";
163
164                         if (isNil) {
165                                 reader.Skip ();
166                                 if (!type.IsValueType || type == typeof (void))
167                                         return null;
168                                 else if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>))
169                                         return null;
170                                 else 
171                                         throw new SerializationException (String.Format ("Value type {0} cannot be null.", type));
172                         }
173
174                         if (resolver != null) {
175                                 Type t;
176                                 if (resolved_qnames.TryGetValue (graph_qname, out t))
177                                         type = t;
178                                 else { // i.e. resolve name only once.
179                                         type = resolver.ResolveName (graph_qname.Name, graph_qname.Namespace, type, default_resolver) ?? type;
180                                         resolved_qnames.Add (graph_qname, type);
181                                         types.Add (type);
182                                 }
183                         }
184
185                         if (KnownTypeCollection.GetPrimitiveTypeFromName (graph_qname) != null) {
186                                 string id = reader.GetAttribute ("Id", KnownTypeCollection.MSSimpleNamespace);
187
188                                 object ret = DeserializePrimitive (type, reader, graph_qname);
189
190                                 if (id != null) {
191                                         if (references.ContainsKey (id))
192                                                 throw new InvalidOperationException (String.Format ("Object with Id '{0}' already exists as '{1}'", id, references [id]));
193                                         references.Add (id, ret);
194                                 }
195                                 return ret;
196                         }
197
198                         return DeserializeByMap (graph_qname, type, reader);
199                 }
200
201                 object DeserializePrimitive (Type type, XmlReader reader, QName qname)
202                 {
203                         bool isDateTimeOffset = false;
204                         // Handle DateTimeOffset type and DateTimeOffset?.
205                         if (type == typeof (DateTimeOffset))
206                                 isDateTimeOffset = true;
207                         else if(type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>)) 
208                                 isDateTimeOffset = type.GetGenericArguments () [0] == typeof (DateTimeOffset);  
209                         // It is the only exceptional type that does not serialize to string but serializes into complex element.
210                         if (isDateTimeOffset) {
211                                 if (reader.IsEmptyElement) {
212                                         reader.Read ();
213                                         return default (DateTimeOffset);
214                                 }
215                                 reader.ReadStartElement ();
216                                 reader.MoveToContent ();
217                                 var date = reader.ReadElementContentAsDateTime ("DateTime", KnownTypeCollection.DefaultClrNamespaceSystem);
218                                 var off = TimeSpan.FromMinutes (reader.ReadElementContentAsInt ("OffsetMinutes", KnownTypeCollection.DefaultClrNamespaceSystem));
219                                 reader.MoveToContent ();
220                                 reader.ReadEndElement ();
221                                 return new DateTimeOffset (DateTime.SpecifyKind (date.ToUniversalTime () + off, DateTimeKind.Unspecified), off);
222                         }
223
224                         string value;
225                         if (reader.IsEmptyElement) {
226                                 reader.Read (); // advance
227                                 if (type.IsValueType)
228                                         return Activator.CreateInstance (type);
229                                 else
230                                         // FIXME: Workaround for creating empty objects of the correct type.
231                                         value = String.Empty;
232                         }
233                         else
234                                 value = reader.ReadElementContentAsString ();
235                         return KnownTypeCollection.PredefinedTypeStringToObject (value, qname.Name, reader);
236                 }
237
238                 object DeserializeByMap (QName name, Type type, XmlReader reader)
239                 {
240                         SerializationMap map = null;
241                         // List<T> and T[] have the same QName, use type to find map work better.
242                         if(name.Name.StartsWith ("ArrayOf", StringComparison.Ordinal) || resolved_qnames.ContainsKey (name))
243                                 map = types.FindUserMap (type);
244                         else
245                                 map = types.FindUserMap (name); // use type when the name is "resolved" one. Otherwise use name (there are cases that type cannot be resolved by type).
246                         if (map == null && (name.Name.StartsWith ("ArrayOf", StringComparison.Ordinal) ||
247                             name.Namespace == KnownTypeCollection.MSArraysNamespace ||
248                             name.Namespace.StartsWith (KnownTypeCollection.DefaultClrNamespaceBase, StringComparison.Ordinal))) {
249                                 var it = GetTypeFromNamePair (name.Name, name.Namespace);
250                                 types.Add (it);
251                                 map = types.FindUserMap (name);
252                         }
253                         if (map == null)
254                                 throw new SerializationException (String.Format ("Unknown type {0} is used for DataContract with reference of name {1}. Any derived types of a data contract or a data member should be added to KnownTypes.", type, name));
255
256                         return map.DeserializeObject (reader, this);
257                 }
258
259                 Type GetTypeFromNamePair (string name, string ns)
260                 {
261                         Type p = KnownTypeCollection.GetPrimitiveTypeFromName (new QName (name, ns));
262                         if (p != null)
263                                 return p;
264                         bool makeArray = false;
265                         if (name.StartsWith ("ArrayOf", StringComparison.Ordinal)) {
266                                 name = name.Substring (7); // strip "ArrayOf"
267                                 if (ns == KnownTypeCollection.MSArraysNamespace)
268                                         return GetTypeFromNamePair (name, String.Empty).MakeArrayType ();
269                                 makeArray = true;
270                         }
271
272                         string dnsb = KnownTypeCollection.DefaultClrNamespaceBase;
273                         string clrns = ns.StartsWith (dnsb, StringComparison.Ordinal) ?  ns.Substring (dnsb.Length) : ns;
274
275                         foreach (var ass in AppDomain.CurrentDomain.GetAssemblies ()) {
276                                 Type [] types;
277
278 #if MOONLIGHT
279                                 try  {
280                                         types = ass.GetTypes ();
281                                 } catch (System.Reflection.ReflectionTypeLoadException rtle) {
282                                         types = rtle.Types;
283                                 }
284 #else
285                                 types = ass.GetTypes ();
286 #endif
287                                 if (types == null)
288                                         continue;
289
290                                 foreach (var t in types) {
291                                         // there can be null entries or exception throw to access the attribute - 
292                                         // at least when some referenced assemblies could not be loaded (affects moonlight)
293                                         if (t == null)
294                                                 continue;
295
296                                         try {
297                                                 var dca = t.GetCustomAttribute<DataContractAttribute> (true);
298                                                 if (dca != null && dca.Name == name && dca.Namespace == ns)
299                                                         return makeArray ? t.MakeArrayType () : t;
300                                         }
301                                         catch (TypeLoadException tle) {
302                                                 Console.Error.WriteLine (tle);
303                                                 continue;
304                                         }
305                                         catch (FileNotFoundException fnfe) {
306                                                 Console.Error.WriteLine (fnfe);
307                                                 continue;
308                                         }
309
310                                         if (clrns != null && t.Name == name && t.Namespace == clrns)
311                                                 return makeArray ? t.MakeArrayType () : t;
312                                 }
313                         }
314                         throw new XmlException (String.Format ("Type not found; name: {0}, namespace: {1}", name, ns));
315                 }
316         }
317 }
318 #endif