2009-07-11 Michael Barker <mike@middlesoft.co.uk>
[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;
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                 // 3.5 SP1 supports deserialization by reference (id->obj).
47                 // Though unlike XmlSerializer, it does not support forward-
48                 // reference resolution i.e. a referenced object must appear
49                 // before any references to it.
50                 Hashtable references = new Hashtable ();
51
52                 public static object Deserialize (XmlReader reader, Type type,
53                         KnownTypeCollection knownTypes, IDataContractSurrogate surrogate,
54                         string name, string ns, bool verifyObjectName)
55                 {
56                         reader.MoveToContent ();
57                         if (verifyObjectName)
58                                 if (reader.NodeType != XmlNodeType.Element ||
59                                     reader.LocalName != name ||
60                                     reader.NamespaceURI != ns)
61                                         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));
62 //                              Verify (knownTypes, type, name, ns, reader);
63                         return new XmlFormatterDeserializer (knownTypes, surrogate).Deserialize (type, reader);
64                 }
65
66                 // Verify the top element name and namespace.
67                 private static void Verify (KnownTypeCollection knownTypes, Type type, string name, string Namespace, XmlReader reader)
68                 {
69                         QName graph_qname = new QName (reader.Name, reader.NamespaceURI);
70                         if (graph_qname.Name == name && graph_qname.Namespace == Namespace)
71                                 return;
72
73                         // <BClass .. i:type="EClass" >..</BClass>
74                         // Expecting type EClass : allowed
75                         // See test Serialize1b, and Serialize1c (for
76                         // negative cases)
77
78                         // Run through inheritance heirarchy .. 
79                         for (Type baseType = type; baseType != null; baseType = baseType.BaseType)
80                                 if (knownTypes.GetQName (baseType) == graph_qname)
81                                         return;
82
83                         QName typeQName = knownTypes.GetQName (type);
84                         throw new SerializationException (String.Format (
85                                 "Expecting element '{0}' from namespace '{1}'. Encountered 'Element' with name '{2}', namespace '{3}'",
86                                 typeQName.Name, typeQName.Namespace, graph_qname.Name, graph_qname.Namespace));
87                 }
88
89                 private XmlFormatterDeserializer (
90                         KnownTypeCollection knownTypes,
91                         IDataContractSurrogate surrogate)
92                 {
93                         this.types = knownTypes;
94                         this.surrogate = surrogate;
95                 }
96
97                 public Hashtable References {
98                         get { return references; }
99                 }
100
101                 // At the beginning phase, we still have to instantiate a new
102                 // target object even if fromContent is true.
103                 public object Deserialize (Type type, XmlReader reader)
104                 {
105                         string label = reader.GetAttribute ("Id", KnownTypeCollection.MSSimpleNamespace);
106                         object o = DeserializeCore (type, reader);
107
108                         if (label != null)
109                                 references.Add (label, o);
110
111                         return o;
112                 }
113
114                 public object DeserializeCore (Type type, XmlReader reader)
115                 {
116                         QName graph_qname = types.GetQName (type);
117                         string itype = reader.GetAttribute ("type", XmlSchema.InstanceNamespace);
118                         if (itype != null) {
119                                 string[] parts = itype.Split (':');
120                                 if (parts.Length > 1)
121                                         graph_qname = new QName (parts [1], reader.LookupNamespace (reader.NameTable.Get (parts[0])));
122                                 else
123                                         graph_qname = new QName (itype, reader.NamespaceURI);
124                         }
125
126                         string label = reader.GetAttribute ("Ref", KnownTypeCollection.MSSimpleNamespace);
127                         if (label != null) {
128                                 object o = references [label];
129                                 if (o == null)
130                                         throw new SerializationException (String.Format ("Deserialized object with reference Id '{0}' was not found", label));
131                                 reader.Skip ();
132                                 return o;
133                         }
134
135                         bool isNil = reader.GetAttribute ("nil", XmlSchema.InstanceNamespace) == "true";
136
137                         if (isNil) {
138                                 reader.Skip ();
139                                 if (!type.IsValueType)
140                                         return null;
141                                 else if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>))
142                                         return null;
143                                 else 
144                                         throw new SerializationException (String.Format ("Value type {0} cannot be null.", type));
145                         }
146
147                         if (KnownTypeCollection.GetPrimitiveTypeFromName (graph_qname.Name) != null) {
148                                 string value;
149                                 if (reader.IsEmptyElement) {
150                                         if (type.IsValueType)
151                                                 return Activator.CreateInstance (type);
152                                         else
153                                                 // FIXME: Workaround for creating empty objects of the correct type.
154                                                 value = String.Empty;
155                                 }
156                                 else
157                                         value = reader.ReadElementContentAsString ();
158                                 return KnownTypeCollection.PredefinedTypeStringToObject (value, graph_qname.Name, reader);
159                         }
160
161                         return DeserializeByMap (graph_qname, type, reader);
162                 }
163
164                 object DeserializeByMap (QName name, Type type, XmlReader reader)
165                 {
166                         SerializationMap map = types.FindUserMap (name);
167                         if (map == null && (name.Namespace == KnownTypeCollection.MSArraysNamespace ||
168                             name.Namespace.StartsWith (KnownTypeCollection.DefaultClrNamespaceBase, StringComparison.Ordinal))) {
169                                 var it = GetTypeFromNamePair (name.Name, name.Namespace);
170                                 types.TryRegister (it);
171                                 map = types.FindUserMap (name);
172                         }
173                         if (map == null)
174                                 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));
175
176                         return map.DeserializeObject (reader, this);
177                 }
178
179                 Type GetTypeFromNamePair (string name, string ns)
180                 {
181                         Type p = KnownTypeCollection.GetPrimitiveTypeFromName (name); // FIXME: namespace?
182                         if (p != null)
183                                 return p;
184                         if (name.StartsWith ("ArrayOf", StringComparison.Ordinal) && ns == KnownTypeCollection.MSArraysNamespace)
185                                 return GetTypeFromNamePair (name.Substring (7), String.Empty).MakeArrayType ();
186
187                         int xlen = KnownTypeCollection.DefaultClrNamespaceBase.Length;
188                         string clrns = ns.Length > xlen ?  ns.Substring (xlen) : null;
189
190                         foreach (var ass in AppDomain.CurrentDomain.GetAssemblies ()) {
191                                 foreach (var t in ass.GetTypes ()) {
192                                         var dca = t.GetCustomAttribute<DataContractAttribute> (true);
193                                         if (dca != null && dca.Name == name && dca.Namespace == ns)
194                                                 return t;
195                                         if (clrns != null && t.Name == name && t.Namespace == clrns)
196                                                 return t;
197                                 }
198                         }
199                         throw new XmlException (String.Format ("Type not found; name: {0}, namespace: {1}", name, ns));
200                 }
201         }
202 }
203 #endif