Merge pull request #347 from JamesB7/master
[mono.git] / mcs / class / System.XML / System.Xml.Serialization / XmlReflectionImporter.cs
index 0a15a15350291b85bafa0e7de5a138d3d7e61a2f..ce274821b43cffc63007c9d0d495cd85b0926caa 100644 (file)
@@ -32,6 +32,7 @@
 //
 
 using System.Collections;
+using System.Collections.Generic;
 using System.Globalization;
 using System.Reflection;
 using System.Xml.Schema;
@@ -117,9 +118,9 @@ namespace System.Xml.Serialization {
                        string ns, 
                        XmlReflectionMember[] members, 
                        bool hasWrapperElement, 
-                       bool writeAccessors)
+                       bool rpc)
                {
-                       return ImportMembersMapping (elementName, ns, members, hasWrapperElement, writeAccessors, true);
+                       return ImportMembersMapping (elementName, ns, members, hasWrapperElement, rpc, true);
                }
 
 #if NET_2_0
@@ -130,39 +131,43 @@ namespace System.Xml.Serialization {
                        string ns, 
                        XmlReflectionMember[] members, 
                        bool hasWrapperElement, 
-                       bool writeAccessors
-                       bool validate)
+                       bool rpc
+                       bool openModel)
                {
-                       return ImportMembersMapping (elementName, ns, members, hasWrapperElement, writeAccessors, validate, XmlMappingAccess.Read | XmlMappingAccess.Write);
+                       return ImportMembersMapping (elementName, ns, members, hasWrapperElement, rpc, openModel, XmlMappingAccess.Read | XmlMappingAccess.Write);
                }
 
 #if NET_2_0
-               [MonoTODO ("writeAccessors, validate, mapping access")]
+               [MonoTODO] // FIXME: handle writeAccessors, validate, and mapping access
                public
 #endif
                XmlMembersMapping ImportMembersMapping (string elementName, 
                        string ns, 
                        XmlReflectionMember[] members, 
                        bool hasWrapperElement, 
-                       bool writeAccessors
-                       bool validate,
+                       bool rpc
+                       bool openModel,
                        XmlMappingAccess access)
                {
 //                     Reset ();       Disabled. See ChangeLog
 
-                       XmlMemberMapping[] mapping = new XmlMemberMapping[members.Length];
+                       ArrayList mapping = new ArrayList ();
                        for (int n=0; n<members.Length; n++)
                        {
                                XmlTypeMapMember mapMem = CreateMapMember (null, members[n], ns);
-                               mapping[n] = new XmlMemberMapping (members[n].MemberName, ns, mapMem, false);
+                               mapMem.GlobalIndex = n;
+                               mapMem.CheckOptionalValueType (members);
+                               mapping.Add (new XmlMemberMapping (members[n].MemberName, ns, mapMem, false));
                        }
                        elementName = XmlConvert.EncodeLocalName (elementName);
-                       XmlMembersMapping mps = new XmlMembersMapping (elementName, ns, hasWrapperElement, false, mapping);
+                       XmlMembersMapping mps = new XmlMembersMapping (elementName, ns, hasWrapperElement, false, (XmlMemberMapping[])mapping.ToArray (typeof(XmlMemberMapping)));
                        mps.RelatedMaps = relatedMaps;
                        mps.Format = SerializationFormat.Literal;
                        Type[] extraTypes = includedTypes != null ? (Type[])includedTypes.ToArray(typeof(Type)) : null;
+#if !NET_2_1
                        mps.Source = new MembersSerializationSource (elementName, hasWrapperElement, members, false, true, ns, extraTypes);
                        if (allowPrivateTypes) mps.Source.CanBeGenerated = false;
+#endif
                        return mps;
                }
 
@@ -176,9 +181,9 @@ namespace System.Xml.Serialization {
                        return ImportTypeMapping (type, null, defaultNamespace);
                }
 
-               public XmlTypeMapping ImportTypeMapping (Type type, XmlRootAttribute group)
+               public XmlTypeMapping ImportTypeMapping (Type type, XmlRootAttribute root)
                {
-                       return ImportTypeMapping (type, group, null);
+                       return ImportTypeMapping (type, root, null);
                }
 
                public XmlTypeMapping ImportTypeMapping (Type type, XmlRootAttribute root, string defaultNamespace)
@@ -223,11 +228,17 @@ namespace System.Xml.Serialization {
                                        default: throw new NotSupportedException ("Type " + typeData.Type.FullName + " not supported for XML stialization");
                                }
 
+#if NET_2_0
+                               // bug #372780
+                               map.SetKey (typeData.Type.ToString ());
+#endif
                                map.RelatedMaps = relatedMaps;
                                map.Format = SerializationFormat.Literal;
                                Type[] extraTypes = includedTypes != null ? (Type[]) includedTypes.ToArray (typeof (Type)) : null;
+#if !NET_2_1
                                map.Source = new XmlTypeSerializationSource (typeData.Type, root, attributeOverrides, defaultNamespace, extraTypes);
                                if (allowPrivateTypes) map.Source.CanBeGenerated = false;
+#endif
                                return map;
                        } catch (InvalidOperationException ex) {
                                throw new InvalidOperationException (string.Format (CultureInfo.InvariantCulture,
@@ -242,7 +253,7 @@ namespace System.Xml.Serialization {
                        string elementName;
                        bool includeInSchema = true;
                        XmlAttributes atts = null;
-                       bool nullable = true;
+                       bool nullable = CanBeNull (typeData);
 
                        if (defaultXmlType == null) defaultXmlType = typeData.XmlType;
 
@@ -263,7 +274,7 @@ namespace System.Xml.Serialization {
 
                        if (atts.XmlType != null)
                        {
-                               if (atts.XmlType.Namespace != null && typeData.SchemaType != SchemaTypes.Enum)
+                               if (atts.XmlType.Namespace != null)
                                        typeNamespace = atts.XmlType.Namespace;
 
                                if (atts.XmlType.TypeName != null && atts.XmlType.TypeName != string.Empty)
@@ -289,7 +300,7 @@ namespace System.Xml.Serialization {
                        XmlTypeMapping map;
                        switch (typeData.SchemaType) {
                                case SchemaTypes.XmlSerializable:
-                                       map = new XmlSerializableMapping (elementName, rootNamespace, typeData, defaultXmlType, typeNamespace);
+                                       map = new XmlSerializableMapping (root, elementName, rootNamespace, typeData, defaultXmlType, typeNamespace);
                                        break;
                                case SchemaTypes.Primitive:
                                        if (!typeData.IsXsdType)
@@ -336,7 +347,22 @@ namespace System.Xml.Serialization {
                        ClassMap classMap = new ClassMap ();
                        map.ObjectMap = classMap;
 
-                       ICollection members = GetReflectionMembers (type);
+                       var members = GetReflectionMembers (type);
+                       bool? isOrderExplicit = null;
+                       foreach (XmlReflectionMember rmember in members)
+                       {
+                               int? order = rmember.XmlAttributes.Order;
+                               if (isOrderExplicit == null)
+                               {
+                                       if (order != null)
+                                               isOrderExplicit = (int) order >= 0;
+                               }
+                               else if (order != null && isOrderExplicit != ((int) order >= 0))
+                                       throw new InvalidOperationException ("Inconsistent XML sequence was detected. If there are XmlElement/XmlArray/XmlAnyElement attributes with explicit Order, then every other member must have an explicit order too.");
+                       }
+                       if (isOrderExplicit == true)
+                               members.Sort ((m1, m2) => (int) m1.XmlAttributes.SortableOrder - (int) m2.XmlAttributes.SortableOrder);
+
                        foreach (XmlReflectionMember rmember in members)
                        {
                                string ns = map.XmlTypeNamespace;
@@ -393,8 +419,10 @@ namespace System.Xml.Serialization {
                                XmlTypeMapMember mem = classMap.XmlTextCollector;
                                if (mem.TypeData.Type != typeof(string) && 
                                   mem.TypeData.Type != typeof(string[]) && 
-                                  mem.TypeData.Type != typeof(object[]) && 
-                                  mem.TypeData.Type != typeof(XmlNode[]))
+#if !MOONLIGHT
+                                  mem.TypeData.Type != typeof(XmlNode[]) && 
+#endif
+                                  mem.TypeData.Type != typeof(object[]))
                                   
                                        throw new InvalidOperationException (String.Format (errSimple2, map.TypeData.TypeName, mem.Name, mem.TypeData.TypeName));
                        }
@@ -475,12 +503,16 @@ namespace System.Xml.Serialization {
 
                        foreach (XmlArrayItemAttribute att in atts.XmlArrayItems)
                        {
+                               if (att.Namespace != null && att.Form == XmlSchemaForm.Unqualified)
+                                       throw new InvalidOperationException ("XmlArrayItemAttribute.Form must not be Unqualified when it has an explicit Namespace value.");
                                if (att.NestingLevel != nestingLevel) continue;
                                Type elemType = (att.Type != null) ? att.Type : itemType;
                                XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (null, TypeTranslator.GetTypeData(elemType, att.DataType));
                                elem.Namespace = att.Namespace != null ? att.Namespace : defaultNamespace;
                                if (elem.Namespace == null) elem.Namespace = "";
                                elem.Form = att.Form;
+                               if (att.Form == XmlSchemaForm.Unqualified)
+                                       elem.Namespace = string.Empty;
                                elem.IsNullable = att.IsNullable && CanBeNull (elem.TypeData);
                                elem.NestingLevel = att.NestingLevel;
 
@@ -601,7 +633,16 @@ namespace System.Xml.Serialization {
                        helper.RegisterClrType (map, type, map.XmlTypeNamespace);
                        return map;
                }
-
+#if MOONLIGHT
+               // Enum.GetNames is not available in SL API
+               public static System.Collections.Generic.IEnumerable<string> GetEnumNames (Type type)
+               {
+                       System.Collections.Generic.List<string> names = new System.Collections.Generic.List<string> ();
+                       foreach (FieldInfo fi in type.GetFields (BindingFlags.Static | BindingFlags.Public))
+                               names.Add (fi.Name);
+                       return names;
+               }
+#endif
                XmlTypeMapping ImportEnumMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
                {
                        Type type = typeData.Type;
@@ -615,10 +656,13 @@ namespace System.Xml.Serialization {
                        map.IsNullable = false;
                        helper.RegisterClrType (map, type, map.XmlTypeNamespace);
 
-                       string [] names = Enum.GetNames (type);
                        ArrayList members = new ArrayList();
-                       foreach (string name in names)
-                       {
+#if MOONLIGHT
+                       foreach (string name in GetEnumNames (type)) {
+#else
+                       string [] names = Enum.GetNames (type);
+                       foreach (string name in names) {
+#endif
                                FieldInfo field = type.GetField (name);
                                string xmlName = null;
                                if (field.IsDefined(typeof(XmlIgnoreAttribute), false))
@@ -660,7 +704,7 @@ namespace System.Xml.Serialization {
                        }
                }
 
-               ICollection GetReflectionMembers (Type type)
+               List<XmlReflectionMember> GetReflectionMembers (Type type)
                {
                        // First we want to find the inheritance hierarchy in reverse order.
                        Type currentType = type;
@@ -728,7 +772,7 @@ namespace System.Xml.Serialization {
                                propList.Insert(currentIndex++, prop);
                        }
 #endif
-                       ArrayList members = new ArrayList();
+                       var members = new List<XmlReflectionMember>();
                        int fieldIndex=0;
                        int propIndex=0;
                        // We now step through the type hierarchy from the base (object) through
@@ -763,7 +807,10 @@ namespace System.Xml.Serialization {
                                                XmlAttributes atts = attributeOverrides[type, prop.Name];
                                                if (atts == null) atts = new XmlAttributes (prop);
                                                if (atts.XmlIgnore) continue;
-                                               if (!prop.CanWrite && (TypeTranslator.GetTypeData (prop.PropertyType).SchemaType != SchemaTypes.Array || prop.PropertyType.IsArray)) continue;
+                                               if (!prop.CanWrite) {
+                                                       if (prop.PropertyType.IsGenericType && TypeData.GetGenericListItemType (prop.PropertyType) == null) continue; // check this before calling GetTypeData() which raises error for missing Add(). See bug #704813.
+                                                       if (TypeTranslator.GetTypeData (prop.PropertyType).SchemaType != SchemaTypes.Array || prop.PropertyType.IsArray) continue;
+                                               }
                                                XmlReflectionMember member = new XmlReflectionMember(prop.Name, prop.PropertyType, atts);
                                                member.DeclaringType = prop.DeclaringType;
                                                members.Add(member);
@@ -771,7 +818,8 @@ namespace System.Xml.Serialization {
                                        else break;
                                }
                        }
-                       return members;         
+                       
+                       return members;
                }
                
                private XmlTypeMapMember CreateMapMember (Type declaringType, XmlReflectionMember rmember, string defaultNamespace)
@@ -780,6 +828,15 @@ namespace System.Xml.Serialization {
                        XmlAttributes atts = rmember.XmlAttributes;
                        TypeData typeData = TypeTranslator.GetTypeData (rmember.MemberType);
 
+                       if (atts.XmlArray != null) {
+                               if (atts.XmlArray.Namespace != null && atts.XmlArray.Form == XmlSchemaForm.Unqualified)
+                                       throw new InvalidOperationException ("XmlArrayAttribute.Form must not be Unqualified when it has an explicit Namespace value.");
+                               if (typeData.SchemaType != SchemaTypes.Array &&
+                                   !(typeData.SchemaType == SchemaTypes.Primitive && typeData.Type == typeof (byte [])))
+                                       throw new InvalidOperationException ("XmlArrayAttribute can be applied to members of array or collection type.");
+                       }
+
+#if !MOONLIGHT
                        if (atts.XmlAnyAttribute != null)
                        {
                                if ( (rmember.MemberType.FullName == "System.Xml.XmlAttribute[]") ||
@@ -790,18 +847,14 @@ namespace System.Xml.Serialization {
                                else
                                        throw new InvalidOperationException ("XmlAnyAttributeAttribute can only be applied to members of type XmlAttribute[] or XmlNode[]");
                        }
-                       else if (atts.XmlAnyElements != null && atts.XmlAnyElements.Count > 0)
+                       else
+#endif
+                       if (atts.XmlAnyElements != null && atts.XmlAnyElements.Count > 0)
                        {
-                               if ( (rmember.MemberType.FullName == "System.Xml.XmlElement[]") ||
-                                        (rmember.MemberType.FullName == "System.Xml.XmlNode[]") ||
-                                        (rmember.MemberType.FullName == "System.Xml.XmlElement"))
-                               {
-                                       XmlTypeMapMemberAnyElement member = new XmlTypeMapMemberAnyElement();
-                                       member.ElementInfo = ImportAnyElementInfo (defaultNamespace, rmember, member, atts);
-                                       mapMember = member;
-                               }
-                               else
-                                       throw new InvalidOperationException ("XmlAnyElementAttribute can only be applied to members of type XmlElement, XmlElement[] or XmlNode[]");
+                               // no XmlNode type check is done here (seealso: bug #553032).
+                               XmlTypeMapMemberAnyElement member = new XmlTypeMapMemberAnyElement();
+                               member.ElementInfo = ImportAnyElementInfo (defaultNamespace, rmember, member, atts);
+                               mapMember = member;
                        }
                        else if (atts.Xmlns)
                        {
@@ -824,8 +877,8 @@ namespace System.Xml.Serialization {
                                mapAttribute.AttributeName = XmlConvert.EncodeLocalName (mapAttribute.AttributeName);
 
                                if (typeData.IsComplexType)
-                                       mapAttribute.MappedType = ImportTypeMapping (typeData.Type, null, mapAttribute.Namespace);
-                               
+                                       mapAttribute.MappedType = ImportTypeMapping (typeData.Type, null, defaultNamespace);
+
                                if (atts.XmlAttribute.Namespace != null && atts.XmlAttribute.Namespace != defaultNamespace)
                                {
                                        if (atts.XmlAttribute.Form == XmlSchemaForm.Unqualified)
@@ -856,7 +909,10 @@ namespace System.Xml.Serialization {
                                {
                                        // A flat list
 
-                                       // TODO: check that it does not have XmlArrayAttribute
+                                       // check that it does not have XmlArrayAttribute
+                                       if (atts.XmlArray != null)
+                                               throw new InvalidOperationException ("XmlArrayAttribute cannot be used with members which also attributed with XmlElementAttribute or XmlTextAttribute.");
+
                                        XmlTypeMapMemberFlatList member = new XmlTypeMapMemberFlatList ();
                                        member.ListMap = new ListMap ();
                                        member.ListMap.ItemInfo = ImportElementInfo (declaringType, XmlConvert.EncodeLocalName (rmember.MemberName), defaultNamespace, typeData.ListItemType, member, atts);
@@ -874,10 +930,18 @@ namespace System.Xml.Serialization {
                                        member.ElementInfo = new XmlTypeMapElementInfoList();
                                        XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, typeData);
                                        elem.ElementName = XmlConvert.EncodeLocalName((atts.XmlArray != null && atts.XmlArray.ElementName.Length != 0) ? atts.XmlArray.ElementName : rmember.MemberName);
+                                       // note that it could be changed below (when Form is Unqualified)
                                        elem.Namespace = (atts.XmlArray != null && atts.XmlArray.Namespace != null) ? atts.XmlArray.Namespace : defaultNamespace;
                                        elem.MappedType = ImportListMapping (rmember.MemberType, null, elem.Namespace, atts, 0);
                                        elem.IsNullable = (atts.XmlArray != null) ? atts.XmlArray.IsNullable : false;
                                        elem.Form = (atts.XmlArray != null) ? atts.XmlArray.Form : XmlSchemaForm.Qualified;
+                                       elem.ExplicitOrder = (atts.XmlArray != null) ? atts.XmlArray.Order : -1;
+                                       // This is a bit tricky, but is done
+                                       // after filling descendant members, so
+                                       // that array items could be serialized
+                                       // with proper namespace.
+                                       if (atts.XmlArray != null && atts.XmlArray.Form == XmlSchemaForm.Unqualified)
+                                               elem.Namespace = String.Empty;
 
                                        member.ElementInfo.Add (elem);
                                        mapMember = member;
@@ -905,7 +969,7 @@ namespace System.Xml.Serialization {
                        Type choiceEnumType = null;
                        
                        XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
-                       ImportTextElementInfo (list, defaultType, member, atts);
+                       ImportTextElementInfo (list, defaultType, member, atts, defaultNamespace);
                        
                        if (atts.XmlChoiceIdentifier != null) {
                                if (cls == null)
@@ -954,8 +1018,9 @@ namespace System.Xml.Serialization {
                                if (elem.Form != XmlSchemaForm.Unqualified)
                                        elem.Namespace = (att.Namespace != null) ? att.Namespace : defaultNamespace;
                                elem.IsNullable = att.IsNullable;
-                               
-                               if (elem.IsNullable && elem.TypeData.IsValueType)
+                               elem.ExplicitOrder = att.Order;
+
+                               if (elem.IsNullable && !elem.TypeData.IsNullable)
                                        throw new InvalidOperationException ("IsNullable may not be 'true' for value type " + elem.TypeData.FullTypeName + " in member '" + defaultName + "'");
                                        
                                if (elem.TypeData.IsComplexType)
@@ -989,7 +1054,7 @@ namespace System.Xml.Serialization {
                                                        + " enumeration value '{1}' for element '{1} from"
                                                        + " namespace '{2}'.", choiceEnumType, elem.ElementName,
                                                        elem.Namespace));
-                                       elem.ChoiceValue = Enum.Parse (choiceEnumType, cname);
+                                       elem.ChoiceValue = Enum.Parse (choiceEnumType, cname, false);
                                }
                                        
                                list.Add (elem);
@@ -1001,8 +1066,9 @@ namespace System.Xml.Serialization {
                {
                        XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
 
-                       ImportTextElementInfo (list, rmember.MemberType, member, atts);
+                       ImportTextElementInfo (list, rmember.MemberType, member, atts, defaultNamespace);
 
+#if !MOONLIGHT // no practical anyElement support
                        foreach (XmlAnyElementAttribute att in atts.XmlAnyElements)
                        {
                                XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(typeof(XmlElement)));
@@ -1018,12 +1084,14 @@ namespace System.Xml.Serialization {
                                        if (att.Namespace != null) 
                                                throw new InvalidOperationException ("The element " + rmember.MemberName + " has been attributed with an XmlAnyElementAttribute and a namespace '" + att.Namespace + "', but no name. When a namespace is supplied, a name is also required. Supply a name or remove the namespace.");
                                }
+                               elem.ExplicitOrder = att.Order;
                                list.Add (elem);
                        }
+#endif
                        return list;
                }
 
-               void ImportTextElementInfo (XmlTypeMapElementInfoList list, Type defaultType, XmlTypeMapMemberElement member, XmlAttributes atts)
+               void ImportTextElementInfo (XmlTypeMapElementInfoList list, Type defaultType, XmlTypeMapMemberElement member, XmlAttributes atts, string defaultNamespace)
                {
                        if (atts.XmlText != null)
                        {
@@ -1035,7 +1103,9 @@ namespace System.Xml.Serialization {
                                        }
                                        defaultType = atts.XmlText.Type;
                                }
+#if !MOONLIGHT
                                if (defaultType == typeof(XmlNode)) defaultType = typeof(XmlText);      // Nodes must be text nodes
+#endif
 
                                XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(defaultType, atts.XmlText.DataType));
 
@@ -1046,6 +1116,8 @@ namespace System.Xml.Serialization {
                                 )
                                        throw new InvalidOperationException ("XmlText cannot be used to encode complex types");
 
+                               if (elem.TypeData.IsComplexType)
+                                       elem.MappedType = ImportTypeMapping (defaultType, null, defaultNamespace);
                                elem.IsTextElement = true;
                                elem.WrappedElement = false;
                                list.Add (elem);
@@ -1054,7 +1126,11 @@ namespace System.Xml.Serialization {
                
                bool CanBeNull (TypeData type)
                {
-                       return (type.SchemaType != SchemaTypes.Primitive || type.Type == typeof (string));
+#if !NET_2_0   // idiotic compatibility
+                       if (type.Type == typeof (XmlQualifiedName))
+                               return false;
+#endif
+                       return !type.Type.IsValueType || type.IsNullable;
                }
                
                public void IncludeType (Type type)
@@ -1087,11 +1163,15 @@ namespace System.Xml.Serialization {
                        if (defaultValue == DBNull.Value || typeData.SchemaType != SchemaTypes.Enum)
                                return defaultValue;
 
+#if MOONLIGHT
+                       string namedValue = (defaultValue as Enum).ToString ("g");
+                       string decimalValue = (defaultValue as Enum).ToString ("d");
+#else
                        // get string representation of enum value
                        string namedValue = Enum.Format (typeData.Type, defaultValue, "g");
                        // get decimal representation of enum value
                        string decimalValue = Enum.Format (typeData.Type, defaultValue, "d");
-
+#endif
                        // if decimal representation matches string representation, then
                        // the value is not defined in the enum type (as the "g" format
                        // will return the decimal equivalent of the value if the value