Merge pull request #900 from Blewzman/FixAggregateExceptionGetBaseException
[mono.git] / mcs / class / System.Runtime.Serialization / System.Runtime.Serialization / XmlFormatterDeserializer.cs
index e1aa3cd03eb4c84b290bf65b17ceea870053131d..52e5bc7bd07965c8d36f45720d84569b9d5716c9 100644 (file)
@@ -27,8 +27,9 @@
 //
 #if NET_2_0
 using System;
-using System.Collections;
+using System.Collections.Generic;
 using System.IO;
+using System.Linq;
 using System.Reflection;
 using System.Runtime.Serialization.Formatters.Binary;
 using System.Xml;
@@ -42,26 +43,32 @@ namespace System.Runtime.Serialization
        {
                KnownTypeCollection types;
                IDataContractSurrogate surrogate;
+               DataContractResolver resolver, default_resolver; // new in 4.0.
                // 3.5 SP1 supports deserialization by reference (id->obj).
                // Though unlike XmlSerializer, it does not support forward-
                // reference resolution i.e. a referenced object must appear
                // before any references to it.
-               Hashtable references = new Hashtable ();
+               Dictionary<string,object> references = new Dictionary<string,object> ();
+               Dictionary<QName,Type> resolved_qnames = new Dictionary<QName,Type> ();
 
-               public static object Deserialize (XmlReader reader, Type type,
-                       KnownTypeCollection knownTypes, IDataContractSurrogate surrogate,
-                       string name, string Namespace, bool verifyObjectName)
+               public static object Deserialize (XmlReader reader, Type declaredType,
+                       KnownTypeCollection knownTypes, IDataContractSurrogate surrogate, DataContractResolver resolver, DataContractResolver defaultResolver,
+                       string name, string ns, bool verifyObjectName)
                {
-                       reader.MoveToContent();
+                       reader.MoveToContent ();
                        if (verifyObjectName)
-                               Verify (knownTypes, type, name, Namespace, reader);
-                       return new XmlFormatterDeserializer (knownTypes, surrogate).Deserialize (type, reader);
+                               if (reader.NodeType != XmlNodeType.Element ||
+                                   reader.LocalName != name ||
+                                   reader.NamespaceURI != ns)
+                                       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));
+//                             Verify (knownTypes, declaredType, name, ns, reader);
+                       return new XmlFormatterDeserializer (knownTypes, surrogate, resolver, defaultResolver).Deserialize (declaredType, reader);
                }
 
                // Verify the top element name and namespace.
                private static void Verify (KnownTypeCollection knownTypes, Type type, string name, string Namespace, XmlReader reader)
                {
-                       QName graph_qname = new QName (reader.Name, reader.NamespaceURI);
+                       QName graph_qname = new QName (reader.LocalName, reader.NamespaceURI);
                        if (graph_qname.Name == name && graph_qname.Namespace == Namespace)
                                return;
 
@@ -83,46 +90,66 @@ namespace System.Runtime.Serialization
 
                private XmlFormatterDeserializer (
                        KnownTypeCollection knownTypes,
-                       IDataContractSurrogate surrogate)
+                       IDataContractSurrogate surrogate,
+                       DataContractResolver resolver,
+                       DataContractResolver defaultResolver)
                {
                        this.types = knownTypes;
                        this.surrogate = surrogate;
+                       this.resolver = resolver;
+                       this.default_resolver = defaultResolver;
                }
 
-               public Hashtable References {
+               public Dictionary<string,object> References {
                        get { return references; }
                }
 
-               // At the beginning phase, we still have to instantiate a new
-               // target object even if fromContent is true.
-               public object Deserialize (Type type, XmlReader reader)
-               {
-                       string label = reader.GetAttribute ("Id", KnownTypeCollection.MSSimpleNamespace);
-                       object o = DeserializeCore (type, reader);
-
-                       if (label != null)
-                               references.Add (label, o);
-
-                       return o;
+               XmlDocument document;
+               
+               XmlDocument XmlDocument {
+                       get { return (document = document ?? new XmlDocument ()); }
                }
 
-               public object DeserializeCore (Type type, XmlReader reader)
+               // This method handles z:Ref, xsi:nil and primitive types, and then delegates to DeserializeByMap() for anything else.
+
+               public object Deserialize (Type type, XmlReader reader)
                {
-                       QName graph_qname = types.GetQName (type);
+                       if (type == typeof (XmlElement))
+                               return XmlDocument.ReadNode (reader);
+                       else if (type == typeof (XmlNode [])) {
+                               reader.ReadStartElement ();
+                               var l = new List<XmlNode> ();
+                               for(; !reader.EOF && reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ())
+                                       l.Add (XmlDocument.ReadNode (reader));
+                               reader.ReadEndElement ();
+                               return l.ToArray ();
+                       }
+                       QName graph_qname = null;
+                       
+                       if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>)) {
+                               Type internal_type = type.GetGenericArguments () [0];
+                               
+                               if (types.FindUserMap(internal_type) != null) {
+                                       graph_qname = types.GetQName (internal_type);
+                               }
+                       }
+                       
+                       if (graph_qname == null)
+                               graph_qname = types.GetQName (type);
+                               
                        string itype = reader.GetAttribute ("type", XmlSchema.InstanceNamespace);
                        if (itype != null) {
-                               string[] parts = itype.Split (':');
+                               string [] parts = itype.Split (':');
                                if (parts.Length > 1)
-                                       graph_qname = new QName (parts [1], reader.LookupNamespace (reader.NameTable.Get (parts[0])));
+                                       graph_qname = new QName (parts [1], reader.LookupNamespace (reader.NameTable.Get (parts [0])));
                                else
-                                       graph_qname = new QName (itype, reader.NamespaceURI);
+                                       graph_qname = new QName (itype, reader.LookupNamespace (String.Empty));
                        }
 
                        string label = reader.GetAttribute ("Ref", KnownTypeCollection.MSSimpleNamespace);
                        if (label != null) {
-Console.WriteLine ("Found reference: " + label);
-                               object o = references [label];
-                               if (o == null)
+                               object o;
+                               if (!references.TryGetValue (label, out o))
                                        throw new SerializationException (String.Format ("Deserialized object with reference Id '{0}' was not found", label));
                                reader.Skip ();
                                return o;
@@ -132,7 +159,7 @@ Console.WriteLine ("Found reference: " + label);
 
                        if (isNil) {
                                reader.Skip ();
-                               if (!type.IsValueType)
+                               if (!type.IsValueType || type == typeof (void))
                                        return null;
                                else if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>))
                                        return null;
@@ -140,39 +167,139 @@ Console.WriteLine ("Found reference: " + label);
                                        throw new SerializationException (String.Format ("Value type {0} cannot be null.", type));
                        }
 
-                       bool isEmpty = reader.IsEmptyElement;
-                       reader.ReadStartElement ();
+                       if (resolver != null) {
+                               Type t;
+                               if (resolved_qnames.TryGetValue (graph_qname, out t))
+                                       type = t;
+                               else { // i.e. resolve name only once.
+                                       type = resolver.ResolveName (graph_qname.Name, graph_qname.Namespace, type, default_resolver) ?? type;
+                                       resolved_qnames.Add (graph_qname, type);
+                                       types.Add (type);
+                               }
+                       }
 
-                       object res = DeserializeContent (graph_qname, type, reader);
+                       if (KnownTypeCollection.GetPrimitiveTypeFromName (graph_qname) != null) {
+                               string id = reader.GetAttribute ("Id", KnownTypeCollection.MSSimpleNamespace);
 
-                       reader.MoveToContent ();
-                       if (reader.NodeType == XmlNodeType.EndElement)
-                               reader.ReadEndElement ();
-                       else if (!isEmpty && reader.NodeType != XmlNodeType.None)
-                               throw new SerializationException (String.Format ("Deserializing type '{3}'. Expecting state 'EndElement'. Encountered state '{0}' with name '{1}' with namespace '{2}'.", reader.NodeType, reader.Name, reader.NamespaceURI, type.FullName));
-                       return res;
+                               object ret = DeserializePrimitive (type, reader, graph_qname);
+
+                               if (id != null) {
+                                       if (references.ContainsKey (id))
+                                               throw new InvalidOperationException (String.Format ("Object with Id '{0}' already exists as '{1}'", id, references [id]));
+                                       references.Add (id, ret);
+                               }
+                               return ret;
+                       }
+
+                       return DeserializeByMap (graph_qname, type, reader);
                }
 
-               object DeserializeContent (QName name, Type type, XmlReader reader)
+               object DeserializePrimitive (Type type, XmlReader reader, QName qname)
                {
-                       if (KnownTypeCollection.IsPrimitiveType (name)) {
-                               string value;
-                               if (reader.NodeType != XmlNodeType.Text)
-                                       if (type.IsValueType)
-                                               return Activator.CreateInstance (type);
-                                       else
-                                               // FIXME: Workaround for creating empty objects of the correct type.
-                                               value = String.Empty;
+                       bool isDateTimeOffset = false;
+                       // Handle DateTimeOffset type and DateTimeOffset?.
+                       if (type == typeof (DateTimeOffset))
+                               isDateTimeOffset = true;
+                       else if(type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>)) 
+                               isDateTimeOffset = type.GetGenericArguments () [0] == typeof (DateTimeOffset);  
+                       // It is the only exceptional type that does not serialize to string but serializes into complex element.
+                       if (isDateTimeOffset) {
+                               if (reader.IsEmptyElement) {
+                                       reader.Read ();
+                                       return default (DateTimeOffset);
+                               }
+                               reader.ReadStartElement ();
+                               reader.MoveToContent ();
+                               var date = reader.ReadElementContentAsDateTime ("DateTime", KnownTypeCollection.DefaultClrNamespaceSystem);
+                               var off = TimeSpan.FromMinutes (reader.ReadElementContentAsInt ("OffsetMinutes", KnownTypeCollection.DefaultClrNamespaceSystem));
+                               reader.MoveToContent ();
+                               reader.ReadEndElement ();
+                               return new DateTimeOffset (DateTime.SpecifyKind (date.ToUniversalTime () + off, DateTimeKind.Unspecified), off);
+                       }
+
+                       string value;
+                       if (reader.IsEmptyElement) {
+                               reader.Read (); // advance
+                               if (type.IsValueType)
+                                       return Activator.CreateInstance (type);
                                else
-                                       value = reader.ReadContentAsString ();
-                               return KnownTypeCollection.PredefinedTypeStringToObject (value, name.Name, reader);
+                                       // FIXME: Workaround for creating empty objects of the correct type.
+                                       value = String.Empty;
                        }
+                       else
+                               value = reader.ReadElementContentAsString ();
+                       return KnownTypeCollection.PredefinedTypeStringToObject (value, qname.Name, reader);
+               }
 
-                       SerializationMap map = types.FindUserMap (name);
+               object DeserializeByMap (QName name, Type type, XmlReader reader)
+               {
+                       SerializationMap map = null;
+                       // List<T> and T[] have the same QName, use type to find map work better.
+                       if(name.Name.StartsWith ("ArrayOf", StringComparison.Ordinal) || resolved_qnames.ContainsKey (name))
+                               map = types.FindUserMap (type);
+                       else
+                               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).
+                       if (map == null && (name.Name.StartsWith ("ArrayOf", StringComparison.Ordinal) ||
+                           name.Namespace == KnownTypeCollection.MSArraysNamespace ||
+                           name.Namespace.StartsWith (KnownTypeCollection.DefaultClrNamespaceBase, StringComparison.Ordinal))) {
+                               var it = GetTypeFromNamePair (name.Name, name.Namespace);
+                               types.Add (it);
+                               map = types.FindUserMap (name);
+                       }
                        if (map == null)
-                               throw new SerializationException (String.Format ("Unknown type {0} is used for DataContract. Any derived types of a data contract or a data member should be added to KnownTypes.", type));
+                               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));
+
+                       return map.DeserializeObject (reader, this);
+               }
+
+               Type GetTypeFromNamePair (string name, string ns)
+               {
+                       Type p = KnownTypeCollection.GetPrimitiveTypeFromName (new QName (name, ns));
+                       if (p != null)
+                               return p;
+                       bool makeArray = false;
+                       if (name.StartsWith ("ArrayOf", StringComparison.Ordinal)) {
+                               name = name.Substring (7); // strip "ArrayOf"
+                               if (ns == KnownTypeCollection.MSArraysNamespace)
+                                       return GetTypeFromNamePair (name, KnownTypeCollection.MSSimpleNamespace).MakeArrayType ();
+                               makeArray = true;
+                       }
+
+                       string dnsb = KnownTypeCollection.DefaultClrNamespaceBase;
+                       string clrns = ns.StartsWith (dnsb, StringComparison.Ordinal) ?  ns.Substring (dnsb.Length) : ns;
+
+                       foreach (var ass in AppDomain.CurrentDomain.GetAssemblies ()) {
+                               Type [] types;
 
-                       return map.DeserializeContent (reader, this);
+                               types = ass.GetTypes ();
+                               if (types == null)
+                                       continue;
+
+                               foreach (var t in types) {
+                                       // there can be null entries or exception throw to access the attribute - 
+                                       // at least when some referenced assemblies could not be loaded (affects moonlight)
+                                       if (t == null)
+                                               continue;
+
+                                       try {
+                                               var dca = t.GetCustomAttribute<DataContractAttribute> (true);
+                                               if (dca != null && dca.Name == name && dca.Namespace == ns)
+                                                       return makeArray ? t.MakeArrayType () : t;
+                                       }
+                                       catch (TypeLoadException tle) {
+                                               Console.Error.WriteLine (tle);
+                                               continue;
+                                       }
+                                       catch (FileNotFoundException fnfe) {
+                                               Console.Error.WriteLine (fnfe);
+                                               continue;
+                                       }
+
+                                       if (clrns != null && t.Name == name && t.Namespace == clrns)
+                                               return makeArray ? t.MakeArrayType () : t;
+                               }
+                       }
+                       throw new XmlException (String.Format ("Type not found; name: {0}, namespace: {1}", name, ns));
                }
        }
 }