2006-02-21 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / System.Xml.Serialization / SoapReflectionImporter.cs
1 // 
2 // System.Xml.Serialization.SoapReflectionImporter 
3 //
4 // Author:
5 //   Tim Coleman (tim@timcoleman.com)
6 //
7 // Copyright (C) Tim Coleman, 2002
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System.Reflection;
32 using System.Xml;
33 using System.Xml.Schema;
34 using System.Collections;
35
36 namespace System.Xml.Serialization {
37         public class SoapReflectionImporter {
38
39                 SoapAttributeOverrides attributeOverrides;
40                 string initialDefaultNamespace;
41                 ArrayList includedTypes;
42                 ArrayList relatedMaps = new ArrayList ();
43                 ReflectionHelper helper = new ReflectionHelper();
44
45                 #region Constructors
46
47                 public SoapReflectionImporter (): this (null, null)
48                 { 
49                 }
50
51                 public SoapReflectionImporter (SoapAttributeOverrides attributeOverrides): this (attributeOverrides, null)
52                 { 
53                 }
54
55                 public SoapReflectionImporter (string defaultNamespace): this (null, defaultNamespace)
56                 {
57                 }
58
59                 public SoapReflectionImporter (SoapAttributeOverrides attributeOverrides, string defaultNamespace)
60                 { 
61                         if (defaultNamespace == null) initialDefaultNamespace = String.Empty;
62                         else initialDefaultNamespace = defaultNamespace;
63
64                         if (attributeOverrides == null) this.attributeOverrides = new SoapAttributeOverrides();
65                         else this.attributeOverrides = attributeOverrides;
66                 }
67
68                 #endregion // Constructors
69
70                 #region Methods
71
72                 public XmlMembersMapping ImportMembersMapping (string elementName, string ns, XmlReflectionMember[] members)
73                 {
74                         return ImportMembersMapping (elementName, ns, members, true, true, false);
75                 }
76
77                 public XmlMembersMapping ImportMembersMapping (string elementName, string ns, XmlReflectionMember[] members, bool hasWrapperElement, bool writeAccessors)
78                 { 
79                         return ImportMembersMapping (elementName, ns, members, hasWrapperElement, writeAccessors, false);
80                 }
81
82                 public XmlMembersMapping ImportMembersMapping (string elementName, string ns, XmlReflectionMember[] members, bool hasWrapperElement, bool writeAccessors, bool validate)
83                 {
84                         elementName = XmlConvert.EncodeLocalName (elementName);
85                         XmlMemberMapping[] mapping = new XmlMemberMapping[members.Length];
86                         for (int n=0; n<members.Length; n++)
87                         {
88                                 XmlTypeMapMember mapMem = CreateMapMember (members[n], ns);
89                                 mapping[n] = new XmlMemberMapping (XmlConvert.EncodeLocalName (members[n].MemberName), ns, mapMem, true);
90                         }
91                         XmlMembersMapping mps = new XmlMembersMapping (elementName, ns, hasWrapperElement, writeAccessors, mapping);
92                         mps.RelatedMaps = relatedMaps;
93                         mps.Format = SerializationFormat.Encoded;
94                         Type[] extraTypes = includedTypes != null ? (Type[])includedTypes.ToArray(typeof(Type)) : null;
95                         mps.Source = new MembersSerializationSource (elementName, hasWrapperElement, members, writeAccessors, false, null, extraTypes);
96                         return mps;
97                 }
98
99                 public XmlTypeMapping ImportTypeMapping (Type type)
100                 { 
101                         return ImportTypeMapping (type, null);
102                 }
103
104                 public XmlTypeMapping ImportTypeMapping (Type type, string defaultNamespace)
105                 { 
106                         if (type == null)
107                                 throw new ArgumentNullException ("type");
108
109                         if (type == typeof (void))
110                                 throw new InvalidOperationException ("Type " + type.Name + " may not be serialized.");
111
112                         string oldNs = initialDefaultNamespace;
113                         if (defaultNamespace == null) defaultNamespace = initialDefaultNamespace;
114                         if (defaultNamespace == null) defaultNamespace = string.Empty;
115                         initialDefaultNamespace = defaultNamespace; 
116
117                         XmlTypeMapping map;
118                         switch (TypeTranslator.GetTypeData(type).SchemaType)
119                         {
120                                 case SchemaTypes.Class: map = ImportClassMapping (type, defaultNamespace); break;
121                                 case SchemaTypes.Array: map = ImportListMapping (type, defaultNamespace); break;
122                                 case SchemaTypes.XmlNode: throw CreateTypeException (type);
123                                 case SchemaTypes.Primitive: map = ImportPrimitiveMapping (type, defaultNamespace); break;
124                                 case SchemaTypes.Enum: map = ImportEnumMapping (type, defaultNamespace); break;
125                                 case SchemaTypes.XmlSerializable:
126                                 default: throw new NotSupportedException ("Type " + type.FullName + " not supported for XML serialization");
127                         }
128                         map.RelatedMaps = relatedMaps;
129                         map.Format = SerializationFormat.Encoded;
130                         Type[] extraTypes = includedTypes != null ? (Type[])includedTypes.ToArray(typeof(Type)) : null;
131                         map.Source = new SoapTypeSerializationSource (type, attributeOverrides, defaultNamespace, extraTypes);
132                         
133                         initialDefaultNamespace = oldNs;
134                         return map;
135                 }
136
137                 XmlTypeMapping CreateTypeMapping (TypeData typeData, string defaultXmlType, string defaultNamespace)
138                 {
139                         string membersNamespace = defaultNamespace;
140                         bool includeInSchema = true;
141
142                         SoapAttributes atts = null;
143                         if (defaultXmlType == null) defaultXmlType = typeData.XmlType;
144
145                         if (!typeData.IsListType)
146                         {
147                                 if (attributeOverrides != null) 
148                                         atts = attributeOverrides[typeData.Type];
149
150                                 if (atts != null && typeData.SchemaType == SchemaTypes.Primitive)
151                                         throw new InvalidOperationException ("SoapType attribute may not be specified for the type " + typeData.FullTypeName);
152                         }
153
154                         if (atts == null) 
155                                 atts = new SoapAttributes (typeData.Type);
156
157                         if (atts.SoapType != null)
158                         {
159                                 if (atts.SoapType.Namespace != null && atts.SoapType.Namespace != string.Empty)
160                                         membersNamespace = atts.SoapType.Namespace;
161
162                                 if (atts.SoapType.TypeName != null && atts.SoapType.TypeName != string.Empty)
163                                         defaultXmlType = XmlConvert.EncodeLocalName (atts.SoapType.TypeName);
164
165                                 includeInSchema = atts.SoapType.IncludeInSchema;
166                         }
167
168                         if (membersNamespace == null) membersNamespace = "";
169                         XmlTypeMapping map = new XmlTypeMapping (defaultXmlType, membersNamespace, typeData, defaultXmlType, membersNamespace);
170                         map.IncludeInSchema = includeInSchema;
171                         relatedMaps.Add (map);
172
173                         return map;
174                 }
175
176                 XmlTypeMapping ImportClassMapping (Type type, string defaultNamespace)
177                 {
178                         if (type.IsValueType) throw CreateStructException (type);
179                         if (type == typeof (object)) defaultNamespace = XmlSchema.Namespace;
180
181                         ReflectionHelper.CheckSerializableType (type, false);
182                                 
183                         TypeData typeData = TypeTranslator.GetTypeData (type);
184                         XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, defaultNamespace));
185                         if (map != null) return map;
186
187                         map = CreateTypeMapping (typeData, null, defaultNamespace);
188                         helper.RegisterClrType (map, type, map.Namespace);
189                         map.MultiReferenceType = true;
190
191                         ClassMap classMap = new ClassMap ();
192                         map.ObjectMap = classMap;
193
194                         // Import members
195
196                         try
197                         {
198                                 ICollection members = GetReflectionMembers (type);
199                                 foreach (XmlReflectionMember rmember in members)
200                                 {
201                                         if (rmember.SoapAttributes.SoapIgnore) continue;
202                                         classMap.AddMember (CreateMapMember (rmember, map.Namespace));
203                                 }
204                         }
205                         catch (Exception ex) 
206                         {
207                                 throw helper.CreateError (map, ex.Message);
208                         }
209
210                         // Import included classes
211
212                         SoapIncludeAttribute[] includes = (SoapIncludeAttribute[])type.GetCustomAttributes (typeof (SoapIncludeAttribute), false);
213                         for (int n=0; n<includes.Length; n++)
214                         {
215                                 Type includedType = includes[n].Type;
216                                 ImportTypeMapping (includedType);
217                         }
218
219                         if (type == typeof (object) && includedTypes != null)
220                         {
221                                 foreach (Type intype in includedTypes)
222                                         map.DerivedTypes.Add (ImportTypeMapping (intype));
223                         }
224
225                         // Register inheritance relations
226
227                         if (type.BaseType != null)
228                         {
229                                 XmlTypeMapping bmap = ImportClassMapping (type.BaseType, defaultNamespace);
230                                 
231                                 if (type.BaseType != typeof (object))
232                                         map.BaseMap = bmap;
233                                         
234                                 // At this point, derived classes of this map must be already registered
235                                 
236                                 RegisterDerivedMap (bmap, map);
237                         }
238                         
239                         return map;
240                 }
241                 
242                 void RegisterDerivedMap (XmlTypeMapping map, XmlTypeMapping derivedMap)
243                 {
244                         map.DerivedTypes.Add (derivedMap);
245                         map.DerivedTypes.AddRange (derivedMap.DerivedTypes);
246                         
247                         if (map.BaseMap != null)
248                                 RegisterDerivedMap (map.BaseMap, derivedMap);
249                         else {
250                                 XmlTypeMapping obmap = ImportTypeMapping (typeof(object));
251                                 if (obmap != map)
252                                         obmap.DerivedTypes.Add (derivedMap);
253                         }
254                 }
255
256                 string GetTypeNamespace (TypeData typeData, string defaultNamespace)
257                 {
258                         string membersNamespace = defaultNamespace;
259
260                         SoapAttributes atts = null;
261
262                         if (!typeData.IsListType)
263                         {
264                                 if (attributeOverrides != null)
265                                         atts = attributeOverrides[typeData.Type];
266                         }
267
268                         if (atts == null)
269                                 atts = new SoapAttributes (typeData.Type);
270
271                         if (atts.SoapType != null)
272                         {
273                                 if (atts.SoapType.Namespace != null && atts.SoapType.Namespace != string.Empty)
274                                         membersNamespace = atts.SoapType.Namespace;
275                         }
276
277                         if (membersNamespace == null) return "";
278                         else return membersNamespace;
279                 }
280                 
281                 XmlTypeMapping ImportListMapping (Type type, string defaultNamespace)
282                 {
283                         TypeData typeData = TypeTranslator.GetTypeData (type);
284                         XmlTypeMapping map = helper.GetRegisteredClrType (type, XmlSerializer.EncodingNamespace);
285                         if (map != null) return map;
286
287                         ListMap obmap = new ListMap ();
288                         TypeData itemTypeData = typeData.ListItemTypeData;
289
290                         map = CreateTypeMapping (typeData, "Array", XmlSerializer.EncodingNamespace);
291                         helper.RegisterClrType (map, type, XmlSerializer.EncodingNamespace);
292                         map.MultiReferenceType = true;
293                         map.ObjectMap = obmap;
294
295                         XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (null, itemTypeData);
296                         
297                         if (elem.TypeData.IsComplexType) {
298                                 elem.MappedType = ImportTypeMapping (typeData.ListItemType, defaultNamespace);
299                                 elem.TypeData = elem.MappedType.TypeData;
300                         }
301                                 
302                         elem.ElementName = "Item";
303                         elem.Namespace = string.Empty;
304                         elem.IsNullable = true; // By default, items are nullable
305
306                         XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
307                         list.Add (elem);
308
309                         obmap.ItemInfo = list;
310                         XmlTypeMapping objMap = ImportTypeMapping (typeof(object), defaultNamespace);
311                         objMap.DerivedTypes.Add (map);
312
313                         // Register any of the including types as a derived class of object
314                         SoapIncludeAttribute[] includes = (SoapIncludeAttribute[])type.GetCustomAttributes (typeof (SoapIncludeAttribute), false);
315                         for (int i = 0; i < includes.Length; i++)
316                         {
317                                 Type includedType = includes[i].Type;
318                                 objMap.DerivedTypes.Add(ImportTypeMapping (includedType, defaultNamespace));
319                         }
320                         
321                         return map;
322                 }
323                 
324                 XmlTypeMapping ImportPrimitiveMapping (Type type, string defaultNamespace)
325                 {
326                         TypeData typeData = TypeTranslator.GetTypeData (type);
327                         XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, defaultNamespace));
328                         if (map != null) return map;
329                         map = CreateTypeMapping (typeData, null, defaultNamespace);
330                         helper.RegisterClrType (map, type, map.Namespace);
331                         return map;
332                 }
333
334
335                 XmlTypeMapping ImportEnumMapping (Type type, string defaultNamespace)
336                 {
337                         TypeData typeData = TypeTranslator.GetTypeData (type);
338                         XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, defaultNamespace));
339                         if (map != null) return map;
340                         
341                         ReflectionHelper.CheckSerializableType (type, false);
342                                 
343                         map = CreateTypeMapping (typeData, null, defaultNamespace);
344                         helper.RegisterClrType (map, type, map.Namespace);
345
346                         map.MultiReferenceType = true;
347                         
348                         string [] names = Enum.GetNames (type);
349                         EnumMap.EnumMapMember[] members = new EnumMap.EnumMapMember[names.Length];
350                         for (int n=0; n<names.Length; n++)
351                         {
352                                 MemberInfo[] mem = type.GetMember (names[n]);
353                                 string xmlName = names[n];
354                                 object[] atts = mem[0].GetCustomAttributes (typeof(SoapEnumAttribute), false);
355                                 if (atts.Length > 0) xmlName = ((SoapEnumAttribute)atts[0]).Name;
356                                 members[n] = new EnumMap.EnumMapMember (XmlConvert.EncodeLocalName (xmlName), names[n]);
357                         }
358
359                         bool isFlags = type.GetCustomAttributes (typeof(FlagsAttribute),false).Length > 0;
360                         map.ObjectMap = new EnumMap (members, isFlags);
361                         ImportTypeMapping (typeof(object), defaultNamespace).DerivedTypes.Add (map);
362                         return map;
363                 }
364
365                 ICollection GetReflectionMembers (Type type)
366                 {
367                         ArrayList members = new ArrayList();
368                         PropertyInfo[] properties = type.GetProperties (BindingFlags.Instance | BindingFlags.Public);
369                         foreach (PropertyInfo prop in properties)
370                         {
371                                 if (!prop.CanRead) continue;
372                                 if (!prop.CanWrite && (TypeTranslator.GetTypeData (prop.PropertyType).SchemaType != SchemaTypes.Array || prop.PropertyType.IsArray))
373                                         continue;
374                                         
375                                 SoapAttributes atts = attributeOverrides[type, prop.Name];
376                                 if (atts == null) atts = new SoapAttributes (prop);
377                                 if (atts.SoapIgnore) continue;
378                                 XmlReflectionMember member = new XmlReflectionMember(prop.Name, prop.PropertyType, atts);
379                                 members.Add (member);
380                         }
381
382                         FieldInfo[] fields = type.GetFields (BindingFlags.Instance | BindingFlags.Public);
383                         foreach (FieldInfo field in fields)
384                         {
385                                 SoapAttributes atts = attributeOverrides[type, field.Name];
386                                 if (atts == null) atts = new SoapAttributes (field);
387                                 if (atts.SoapIgnore) continue;
388                                 XmlReflectionMember member = new XmlReflectionMember(field.Name, field.FieldType, atts);
389                                 members.Add (member);
390                         }
391                         return members;
392                 }
393                 
394                 private XmlTypeMapMember CreateMapMember (XmlReflectionMember rmember, string defaultNamespace)
395                 {
396                         XmlTypeMapMember mapMember;
397                         SoapAttributes atts = rmember.SoapAttributes;
398                         TypeData typeData = TypeTranslator.GetTypeData (rmember.MemberType);
399
400                         if (atts.SoapAttribute != null)
401                         {
402                                 // An attribute
403
404                                 if (atts.SoapElement != null)
405                                         throw new Exception ("SoapAttributeAttribute and SoapElementAttribute cannot be applied to the same member");
406
407                                 XmlTypeMapMemberAttribute mapAttribute = new XmlTypeMapMemberAttribute ();
408                                 if (atts.SoapAttribute.AttributeName.Length == 0) 
409                                         mapAttribute.AttributeName = XmlConvert.EncodeLocalName (rmember.MemberName);
410                                 else 
411                                         mapAttribute.AttributeName = XmlConvert.EncodeLocalName (atts.SoapAttribute.AttributeName);
412
413                                 mapAttribute.Namespace = (atts.SoapAttribute.Namespace != null) ? atts.SoapAttribute.Namespace : "";
414                                 if (typeData.IsComplexType)
415                                         mapAttribute.MappedType = ImportTypeMapping (typeData.Type, defaultNamespace);
416
417                                 typeData = TypeTranslator.GetTypeData (rmember.MemberType, atts.SoapAttribute.DataType);
418                                 mapMember = mapAttribute;
419                         }
420                         else
421                         {
422                                 if (typeData.SchemaType == SchemaTypes.Array) mapMember = new XmlTypeMapMemberList ();
423                                 else mapMember = new XmlTypeMapMemberElement ();
424
425                                 if (atts.SoapElement != null && atts.SoapElement.DataType.Length != 0)
426                                         typeData = TypeTranslator.GetTypeData (rmember.MemberType, atts.SoapElement.DataType);
427
428                                 // Creates an ElementInfo that identifies the element
429                                 XmlTypeMapElementInfoList infoList = new XmlTypeMapElementInfoList();
430                                 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (mapMember, typeData);
431
432                                 elem.ElementName = XmlConvert.EncodeLocalName ((atts.SoapElement != null && atts.SoapElement.ElementName.Length != 0) ? atts.SoapElement.ElementName : rmember.MemberName);
433                                 elem.Namespace = string.Empty;
434                                 elem.IsNullable = (atts.SoapElement != null) ? atts.SoapElement.IsNullable : false;
435                                 if (typeData.IsComplexType)
436                                         elem.MappedType = ImportTypeMapping (typeData.Type, defaultNamespace);
437                                 
438                                 infoList.Add (elem);
439                                 ((XmlTypeMapMemberElement)mapMember).ElementInfo = infoList;
440                         }
441
442                         mapMember.TypeData = typeData;
443                         mapMember.Name = rmember.MemberName;
444                         mapMember.IsReturnValue = rmember.IsReturnValue;
445                         return mapMember;
446                 }
447                 
448                 public void IncludeType (Type type)
449                 {
450                         if (type == null)
451                                 throw new ArgumentNullException ("type");
452
453                         if (includedTypes == null) includedTypes = new ArrayList ();
454                         if (!includedTypes.Contains (type))
455                                 includedTypes.Add (type);
456                 }
457
458                 public void IncludeTypes (ICustomAttributeProvider provider)
459                 { 
460                         object[] ats = provider.GetCustomAttributes (typeof(SoapIncludeAttribute), true);
461                         foreach (SoapIncludeAttribute at in ats)
462                                 IncludeType (at.Type);
463                 }
464
465                 Exception CreateTypeException (Type type)
466                 {
467                         return new NotSupportedException ("The type " + type.FullName + " may not be serialized with SOAP-encoded messages. Set the Use for your message to Literal");
468                 }
469
470                 Exception CreateStructException (Type type)
471                 {
472                         return new NotSupportedException ("Cannot serialize " + type.FullName + ". Nested structs are not supported with encoded SOAP");
473                 }
474
475
476                 
477
478                 #endregion // Methods
479         }
480 }