2009-03-12 Atsushi Enomoto <atsushi@ximian.com>
authorAtsushi Eno <atsushieno@gmail.com>
Thu, 12 Mar 2009 08:37:33 +0000 (08:37 -0000)
committerAtsushi Eno <atsushieno@gmail.com>
Thu, 12 Mar 2009 08:37:33 +0000 (08:37 -0000)
* SerializationMap.cs, KnownTypeCollection.cs :
  added support for dictionary serialization (not for
  CollectionDataContract-attributed dictionary yet).

* XmlObjectSerializerTest.cs : added tests for IDictionary
  serialization (both generic and non-generic).

svn path=/trunk/mcs/; revision=129125

mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/ChangeLog
mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/KnownTypeCollection.cs
mcs/class/System.Runtime.Serialization/System.Runtime.Serialization/SerializationMap.cs
mcs/class/System.Runtime.Serialization/Test/System.Runtime.Serialization/ChangeLog
mcs/class/System.Runtime.Serialization/Test/System.Runtime.Serialization/XmlObjectSerializerTest.cs

index aa0fa8142d2d6fc004fb6e9dad140ecbc6543c2a..3f7ef97d60d6925a2cd98237f74c86e38f6d61ea 100755 (executable)
@@ -1,3 +1,9 @@
+2009-03-12  Atsushi Enomoto  <atsushi@ximian.com>
+
+       * SerializationMap.cs, KnownTypeCollection.cs :
+         added support for dictionary serialization (not for
+         CollectionDataContract-attributed dictionary yet).
+
 2009-03-12  Atsushi Enomoto  <atsushi@ximian.com>
 
        * SerializationMap.cs : cosmetic !NET_2_1 fix (no effect).
index 2ac19f09ee67aad4fd7ef6f2588c1d5e7d37c26b..4ce0c05c457a1522b5e4a721448f2d567b9058e5 100644 (file)
@@ -30,6 +30,7 @@ using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
+using System.Linq;
 using System.Reflection;
 using System.Xml;
 using System.Xml.Schema;
@@ -557,6 +558,9 @@ namespace System.Runtime.Serialization
                        if (RegisterCollectionContract (type) != null)
                                return true;
 
+                       if (RegisterDictionary (type) != null)
+                               return true;
+
                        if (RegisterCollection (type) != null)
                                return true;
 
@@ -631,6 +635,24 @@ namespace System.Runtime.Serialization
                        return ret;
                }
 
+               private DictionaryTypeMap RegisterDictionary (Type type)
+               {
+                       if (!type.GetInterfaces ().Any (t => t == typeof (IDictionary) || t.FullName.StartsWith ("System.Collections.Generic.IDictionary")))
+                               return null;
+
+                       DictionaryTypeMap ret =
+                               new DictionaryTypeMap (type, this);
+
+                       if (FindUserMap (ret.XmlName) != null)
+                               throw new InvalidOperationException (String.Format ("Failed to add type {0} to known type collection. There already is a registered type for XML name {1}", type, ret.XmlName));
+                       contracts.Add (ret);
+
+                       TryRegister (ret.KeyType);
+                       TryRegister (ret.ValueType);
+
+                       return ret;
+               }
+
                private SerializationMap RegisterSerializable (Type type)
                {
                        QName qname = GetSerializableQName (type);
index 9ff78e77564de2646a73663db3e97ff9cdf38ad6..bd8defd480f0c9ff620fe80445547e8f39e51d2d 100644 (file)
@@ -86,7 +86,6 @@ namespace System.Runtime.Serialization
 
                public readonly KnownTypeCollection KnownTypes;
                public readonly Type RuntimeType;
-               public readonly QName XmlName;
                public bool IsReference; // new in 3.5 SP1
                public List<DataMemberInfo> Members;
 #if !NET_2_1
@@ -109,6 +108,8 @@ namespace System.Runtime.Serialization
                        Members = new List<DataMemberInfo> ();
                }
 
+               public QName XmlName { get; set; }
+
                public CollectionDataContractAttribute GetCollectionDataContractAttribute (Type type)
                {
                        object [] atts = type.GetCustomAttributes (
@@ -465,7 +466,7 @@ namespace System.Runtime.Serialization
                                        continue;
                                KnownTypes.TryRegister (pi.PropertyType);
                                var map = KnownTypes.FindUserMap (pi.PropertyType);
-                               if (!pi.CanRead || (!pi.CanWrite && !(map is CollectionTypeMap)))
+                               if (!pi.CanRead || (!pi.CanWrite && !(map is ICollectionTypeMap)))
                                        throw new InvalidDataContractException (String.Format (
                                                        "DataMember property '{0}' on type '{1}' must have both getter and setter.", pi, pi.DeclaringType));
                                data_members.Add (CreateDataMemberInfo (dma, pi, pi.PropertyType));
@@ -536,7 +537,11 @@ namespace System.Runtime.Serialization
                }
        }
 
-       internal class CollectionTypeMap : SerializationMap
+       internal interface ICollectionTypeMap
+       {
+       }
+
+       internal class CollectionTypeMap : SerializationMap, ICollectionTypeMap
        {
                Type element_type;
                internal QName element_qname;
@@ -559,7 +564,7 @@ namespace System.Runtime.Serialization
                                                break;
                                        }
                                if (add_method == null)
-                                       add_method = type.GetMethod ("Add", new Type [] {icoll.GetGenericArguments() [0]});
+                                       add_method = type.GetMethod ("Add", icoll.GetGenericArguments());
                        }
                }
 
@@ -660,6 +665,166 @@ namespace System.Runtime.Serialization
 #endif
        }
 
+       internal class DictionaryTypeMap : SerializationMap, ICollectionTypeMap
+       {
+               Type key_type, value_type;
+               internal QName dict_qname, item_qname, key_qname, value_qname;
+               MethodInfo add_method;
+
+               public DictionaryTypeMap (
+                       Type type, KnownTypeCollection knownTypes)
+                       : base (type, QName.Empty, knownTypes)
+               {
+                       key_type = typeof (object);
+                       value_type = typeof (object);
+
+                       var idic = RuntimeType.GetInterfaces ().FirstOrDefault (
+                               iface => iface.IsGenericType && iface.GetGenericTypeDefinition () == typeof (IDictionary<,>));
+                       if (idic != null) {
+                               var imap = RuntimeType.GetInterfaceMap (idic);
+                               for (int i = 0; i < imap.InterfaceMethods.Length; i++)
+                                       if (imap.InterfaceMethods [i].Name == "Add") {
+                                               add_method = imap.TargetMethods [i];
+                                               break;
+                                       }
+                               var argtypes = idic.GetGenericArguments();
+                               key_type = argtypes [0];
+                               value_type = argtypes [1];
+                               if (add_method == null)
+                                       add_method = type.GetMethod ("Add", argtypes);
+                       }
+
+                       XmlName = GetDictionaryQName ();
+                       item_qname = GetItemQName ();
+                       key_qname = GetKeyQName ();
+                       value_qname = GetValueQName ();
+               }
+
+               public Type KeyType { get { return key_type; } }
+               public Type ValueType { get { return value_type; } }
+
+               static readonly QName kvpair_key_qname = new QName ("Key", KnownTypeCollection.MSArraysNamespace);
+               static readonly QName dentry_key_qname = new QName ("key", KnownTypeCollection.MSArraysNamespace);
+               static readonly QName kvpair_value_qname = new QName ("Value", KnownTypeCollection.MSArraysNamespace);
+               static readonly QName dentry_value_qname = new QName ("value", KnownTypeCollection.MSArraysNamespace);
+
+               internal virtual QName GetDictionaryQName ()
+               {
+                       if (dict_qname == null)
+                               dict_qname = new QName ("ArrayOf" + GetItemQName ().Name, KnownTypeCollection.MSArraysNamespace);
+                       return dict_qname;
+               }
+
+               internal virtual QName GetItemQName ()
+               {
+                       if (item_qname == null)
+                               item_qname = new QName ("KeyValueOf" + KnownTypes.GetQName (key_type).Name + KnownTypes.GetQName (value_type).Name, KnownTypeCollection.MSArraysNamespace);
+                       return item_qname;
+               }
+
+               internal virtual QName GetKeyQName ()
+               {
+                       if (add_method != null)
+                               return kvpair_key_qname;
+                       else
+                               return dentry_key_qname;
+               }
+
+               internal virtual QName GetValueQName ()
+               {
+                       if (add_method != null)
+                               return kvpair_value_qname;
+                       else
+                               return dentry_value_qname;
+               }
+
+               internal virtual string CurrentNamespace {
+                       get {
+                               string ns = item_qname.Namespace;
+                               if (ns == KnownTypeCollection.MSSimpleNamespace)
+                                       ns = KnownTypeCollection.MSArraysNamespace;
+                               return ns;
+                       }
+               }
+
+               Type pair_type;
+               PropertyInfo pair_key_property, pair_value_property;
+
+               public override void SerializeNonReference (object graph,
+                       XmlFormatterSerializer serializer)
+               {
+                       if (add_method != null) { // generic
+                               if (pair_type == null) {
+                                       pair_type = typeof (KeyValuePair<,>).MakeGenericType (add_method.DeclaringType.GetGenericArguments ());
+                                       pair_key_property = pair_type.GetProperty ("Key");
+                                       pair_value_property = pair_type.GetProperty ("Value");
+                               }
+                               foreach (object p in (IEnumerable) graph) {
+                                       serializer.WriteStartElement (item_qname.Name, item_qname.Namespace, CurrentNamespace);
+                                       serializer.WriteStartElement (key_qname.Name, key_qname.Namespace, CurrentNamespace);
+                                       serializer.Serialize (pair_key_property.PropertyType, pair_key_property.GetValue (p, null));
+                                       serializer.WriteEndElement ();
+                                       serializer.WriteStartElement (value_qname.Name, value_qname.Namespace, CurrentNamespace);
+                                       serializer.Serialize (pair_value_property.PropertyType, pair_value_property.GetValue (p, null));
+                                       serializer.WriteEndElement ();
+                                       serializer.WriteEndElement ();
+                               }
+                       } else { // non-generic
+                               foreach (DictionaryEntry p in (IEnumerable) graph) {
+                                       serializer.WriteStartElement (item_qname.Name, item_qname.Namespace, CurrentNamespace);
+                                       serializer.WriteStartElement (key_qname.Name, key_qname.Namespace, CurrentNamespace);
+                                       serializer.Serialize (key_type, p.Key);
+                                       serializer.WriteEndElement ();
+                                       serializer.WriteStartElement (value_qname.Name, value_qname.Namespace, CurrentNamespace);
+                                       serializer.Serialize (value_type, p.Value);
+                                       serializer.WriteEndElement ();
+                                       serializer.WriteEndElement ();
+                               }
+                       }
+               }
+
+               public override object DeserializeContent(XmlReader reader, XmlFormatterDeserializer deserializer)
+               {
+#if NET_2_1 // FIXME: is it fine?
+                       object instance = Activator.CreateInstance (RuntimeType);
+#else
+                       object instance = Activator.CreateInstance (RuntimeType, true);
+#endif
+                       int depth = reader.NodeType == XmlNodeType.None ? reader.Depth : reader.Depth - 1;
+                       while (reader.NodeType == XmlNodeType.Element && reader.Depth > depth) {
+                               if (reader.IsEmptyElement)
+                                       throw new XmlException (String.Format ("Unexpected empty element for dictionary entry: name {0}", reader.Name));
+                               reader.ReadStartElement (item_qname.Name, item_qname.Namespace);
+                               reader.MoveToContent ();
+                               object key = deserializer.Deserialize (key_type, reader);
+                               reader.MoveToContent ();
+                               object val = deserializer.Deserialize (value_type, reader);
+                               reader.ReadEndElement (); // of pair
+
+                               if (instance is IDictionary)
+                                       ((IDictionary)instance).Add (key, val);
+                               else if (add_method != null)
+                                       add_method.Invoke (instance, new object [] {key, val});
+                               else
+                                       throw new NotImplementedException (String.Format ("Type {0} is not supported", RuntimeType));
+                       }
+                       return instance;
+               }
+
+               public override List<DataMemberInfo> GetMembers ()
+               {
+                       //Shouldn't come here at all!
+                       throw new NotImplementedException ();
+               }
+               
+#if !NET_2_1
+               public override XmlSchemaType GetSchemaType (XmlSchemaSet schemas, Dictionary<QName, XmlSchemaType> generated_schema_types)
+               {
+                       throw new NotImplementedException ();
+               }
+#endif
+       }
+
        internal class SharedTypeMap : SerializationMap
        {
                public SharedTypeMap (
index ef66c700597406e559e4f5b32429c6310268f516..46bc292322943083c5e80756b97cb4b3ef954fb9 100644 (file)
@@ -1,3 +1,8 @@
+2009-03-12  Atsushi Enomoto  <atsushi@ximian.com>
+
+       * XmlObjectSerializerTest.cs : added tests for IDictionary
+         serialization (both generic and non-generic).
+
 2009-03-11  Atsushi Enomoto  <atsushi@ximian.com>
 
        * XmlObjectSerializerTest.cs : test for generic IList of
index f97bb4df56514c6c71e74617209c0ad786c854d8..98844e5ffa2a083f1a0e2fd014e7d00383822ddd 100644 (file)
@@ -1102,18 +1102,14 @@ namespace MonoTests.System.Runtime.Serialization
 
                        var ds = new DataContractSerializer (typeof (List<KeyValuePair<string,string>>));
                        var d = new List<KeyValuePair<string,string>> ();
-                       //d ["foo"] = "bar";
-                       //d.Add (new DictionaryEntry ("foo", "bar"));
                        d.Add (new KeyValuePair<string,string> ("foo", "bar"));
                        var sw = new StringWriter ();
                        using (var xw = XmlWriter.Create (sw))
                                ds.WriteObject (xw, d);
                        Assert.AreEqual (xml, sw.ToString (), "#1");
                        d = (List<KeyValuePair<string,string>>) ds.ReadObject (XmlReader.Create (new StringReader (xml)));
-                       //Console.WriteLine ("{0}: {1}", d.Count, d ["foo"]);
-                       Console.WriteLine ("{0}: {1}", d.Count, d [0].Value);
                        Assert.AreEqual (1, d.Count, "#2");
-                       Assert.AreEqual ("bar", d [0].Value);
+                       Assert.AreEqual ("bar", d [0].Value, "#3");
                }
 
                [Test]
@@ -1131,7 +1127,41 @@ namespace MonoTests.System.Runtime.Serialization
                        Assert.IsTrue (sw.ToString ().IndexOf ("i:type") >= 0);
                        d = (List<DictionaryEntry>) ds.ReadObject (XmlReader.Create (new StringReader (xml)));
                        Assert.AreEqual (1, d.Count, "#2");
-                       Assert.AreEqual ("bar", d [0].Value);
+                       Assert.AreEqual ("bar", d [0].Value, "#3");
+               }
+
+               [Test]
+               public void GenericDictionarySerialization ()
+               {
+                       string xml = @"<?xml version='1.0' encoding='utf-16'?><ArrayOfKeyValueOfstringstring xmlns:i='http://www.w3.org/2001/XMLSchema-instance' xmlns='http://schemas.microsoft.com/2003/10/Serialization/Arrays'><KeyValueOfstringstring><Key>foo</Key><Value>bar</Value></KeyValueOfstringstring></ArrayOfKeyValueOfstringstring>".Replace ('\'', '"');
+
+                       var ds = new DataContractSerializer (typeof (Dictionary<string,string>));
+                       var d = new Dictionary<string,string> ();
+                       d ["foo"] = "bar";
+                       var sw = new StringWriter ();
+                       using (var xw = XmlWriter.Create (sw))
+                               ds.WriteObject (xw, d);
+                       Assert.AreEqual (xml, sw.ToString (), "#1");
+                       d = (Dictionary<string,string>) ds.ReadObject (XmlReader.Create (new StringReader (xml)));
+                       Assert.AreEqual (1, d.Count, "#2");
+                       Assert.AreEqual ("bar", d ["foo"], "#3");
+               }
+
+               [Test]
+               public void HashtableSerialization ()
+               {
+                       string xml = @"<?xml version='1.0' encoding='utf-16'?><ArrayOfKeyValueOfanyTypeanyType xmlns:i='http://www.w3.org/2001/XMLSchema-instance' xmlns='http://schemas.microsoft.com/2003/10/Serialization/Arrays'><KeyValueOfanyTypeanyType><Key xmlns:d3p1='http://www.w3.org/2001/XMLSchema' i:type='d3p1:string'>foo</Key><Value xmlns:d3p1='http://www.w3.org/2001/XMLSchema' i:type='d3p1:string'>bar</Value></KeyValueOfanyTypeanyType></ArrayOfKeyValueOfanyTypeanyType>".Replace ('\'', '"');
+
+                       var ds = new DataContractSerializer (typeof (Hashtable));
+                       var d = new Hashtable ();
+                       d ["foo"] = "bar";
+                       var sw = new StringWriter ();
+                       using (var xw = XmlWriter.Create (sw))
+                               ds.WriteObject (xw, d);
+                       Assert.AreEqual (xml, sw.ToString (), "#1");
+                       d = (Hashtable) ds.ReadObject (XmlReader.Create (new StringReader (xml)));
+                       Assert.AreEqual (1, d.Count, "#2");
+                       Assert.AreEqual ("bar", d ["foo"], "#3");
                }
 
                private T Deserialize<T> (string xml)