Merge pull request #912 from Treer/patch-1
[mono.git] / mcs / class / System.XML / System.Xml.Serialization / TypeData.cs
1 //
2 // System.Xml.Serialization.TypeData
3 //
4 // Authors:
5 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 //  Lluis Sanchez Gual (lluis@ximian.com)
7 //  Atsushi Enomoto (atsushi@ximian.com)
8 //
9 // (C) 2002 Ximian, Inc (http://www.ximian.com)
10 //
11
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 using System;
34 using System.Collections;
35 #if NET_2_0
36 using System.Collections.Generic;
37 #endif
38 using System.Globalization;
39 using System.Reflection;
40 using System.Text;
41 using System.Xml.Schema;
42
43 namespace System.Xml.Serialization
44 {
45         [AttributeUsage (AttributeTargets.Class, AllowMultiple = false)]
46         internal class XmlTypeConvertorAttribute : Attribute
47         {
48                 /*
49                  * Bug #12571:
50                  * 
51                  * System.Xml.Linq.XElement should be deserializable from an XmlElement.
52                  * 
53                  * Types can now register a custom deserializer by adding this custom attribute.
54                  * Method is the name of a private 'static method (static object)' method that will
55                  * be invoked to construct an instance of the object.
56                  * 
57                  */
58                 public string Method {
59                         get;
60                         private set;
61                 }
62
63                 public XmlTypeConvertorAttribute (string method)
64                 {
65                         Method = method;
66                 }
67         }
68
69         internal class TypeData
70         {
71                 Type type;
72                 string elementName;
73                 SchemaTypes sType;
74                 Type listItemType;
75                 string typeName;
76                 string fullTypeName;
77                 string csharpName;
78                 string csharpFullName;
79                 TypeData listItemTypeData;
80                 TypeData listTypeData;
81                 TypeData mappedType;
82                 XmlSchemaPatternFacet facet;
83                 MethodInfo typeConvertor;
84                 bool hasPublicConstructor = true;
85                 bool nullableOverride;
86
87                 public TypeData (Type type, string elementName, bool isPrimitive) :
88                         this(type, elementName, isPrimitive, null, null) {}
89
90                 public TypeData (Type type, string elementName, bool isPrimitive, TypeData mappedType, XmlSchemaPatternFacet facet)
91                 {
92 #if NET_2_0
93                         if (type.IsGenericTypeDefinition)
94                                 throw new InvalidOperationException ("Generic type definition cannot be used in serialization. Only specific generic types can be used.");
95 #endif
96                         this.mappedType = mappedType;
97                         this.facet = facet;
98                         this.type = type;
99                         this.typeName = type.Name;
100                         this.fullTypeName = type.FullName.Replace ('+', '.');
101
102                         if (isPrimitive)
103                                 sType = SchemaTypes.Primitive;
104                         else
105                         {
106                                 if (type.IsEnum)
107                                         sType = SchemaTypes.Enum;
108                                 else if (typeof(IXmlSerializable).IsAssignableFrom (type))
109                                         sType = SchemaTypes.XmlSerializable;
110                                 else if (typeof (System.Xml.XmlNode).IsAssignableFrom (type))
111                                         sType = SchemaTypes.XmlNode;
112                                 else if (type.IsArray || typeof(IEnumerable).IsAssignableFrom (type))
113                                         sType = SchemaTypes.Array;
114                                 else
115                                         sType = SchemaTypes.Class;
116                         }
117                         
118                         if (IsListType)
119                                 this.elementName = TypeTranslator.GetArrayName (ListItemTypeData.XmlType);
120                         else
121                                 this.elementName = elementName;
122
123                         if (sType == SchemaTypes.Array || sType == SchemaTypes.Class) {
124                                 hasPublicConstructor = !type.IsInterface && (type.IsArray || type.GetConstructor (Type.EmptyTypes) != null || type.IsAbstract || type.IsValueType);
125                         }
126
127                         LookupTypeConvertor ();
128                 }
129
130                 internal TypeData (string typeName, string fullTypeName, string xmlType, SchemaTypes schemaType, TypeData listItemTypeData)
131                 {
132                         this.elementName = xmlType;
133                         this.typeName = typeName;
134                         this.fullTypeName = fullTypeName.Replace ('+', '.');
135                         this.listItemTypeData = listItemTypeData;
136                         this.sType = schemaType;
137                         this.hasPublicConstructor = true;
138
139                         LookupTypeConvertor ();
140                 }
141
142                 void LookupTypeConvertor ()
143                 {
144 #if NET_4_5
145                         // We only need this for System.Xml.Linq.
146                         var convertor = type.GetCustomAttribute<XmlTypeConvertorAttribute> ();
147                         if (convertor != null)
148                                 typeConvertor = type.GetMethod (convertor.Method, BindingFlags.Static | BindingFlags.NonPublic);
149 #endif
150                 }
151
152                 internal void ConvertForAssignment (ref object value)
153                 {
154                         // Has this object registered a custom type converter?
155                         if (typeConvertor != null)
156                                 value = typeConvertor.Invoke (null, new object[] { value });
157                 }
158
159                 public string TypeName
160                 {
161                         get {
162                                 return typeName;
163                         }
164                 }
165                                 
166                 public string XmlType
167                 {
168                         get {
169                                 return elementName;
170                         }
171                 }
172                                 
173                 public Type Type
174                 {
175                         get {
176                                 return type;
177                         }
178                 }
179                                 
180                 public string FullTypeName
181                 {
182                         get {
183                                 return fullTypeName;
184                         }
185                 }
186
187                 public string CSharpName
188                 {
189                         get {
190                                 if (csharpName == null)
191                                         csharpName = (Type == null) ? TypeName : ToCSharpName (Type, false);
192                                 return csharpName;
193                         }
194                 }
195
196                 public string CSharpFullName
197                 {
198                         get {
199                                 if (csharpFullName == null)
200                                         csharpFullName = (Type == null) ? TypeName : ToCSharpName (Type, true);
201                                 return csharpFullName;
202                         }
203                 }
204
205                 // static Microsoft.CSharp.CSharpCodeProvider csprovider =
206                 //      new Microsoft.CSharp.CSharpCodeProvider ();
207
208                 public static string ToCSharpName (Type type, bool full)
209                 {
210                         // return csprovider.GetTypeOutput (new System.CodeDom.CodeTypeReference (type));
211                         StringBuilder sb = new StringBuilder ();
212                         
213                         if (type.IsArray) {
214                                 sb.Append (ToCSharpName (type.GetElementType (), full));
215                                 sb.Append ('[');
216                                 int rank = type.GetArrayRank ();
217                                 for (int i = 1; i < rank; i++)
218                                         sb.Append (',');
219                                 sb.Append (']');
220                                 return sb.ToString ();
221                         }
222 #if NET_2_0
223                         // Generic nested types return the complete list of type arguments,
224                         // including type arguments for the declaring class. This requires
225                         // some special handling
226                         if (type.IsGenericType && !type.IsGenericTypeDefinition) {
227                                 Type[] args = type.GetGenericArguments ();
228                                 int nt = args.Length - 1;
229                                 Type pt = type;
230                                 // Loop throguh the declaring class chain, consuming type arguments for every
231                                 // generic class in the chain
232                                 while (pt != null) {
233                                         if (sb.Length > 0)
234                                                 sb.Insert (0, '.');
235                                         int i = pt.Name.IndexOf ('`');
236                                         if (i != -1) {
237                                                 int na = nt - int.Parse (pt.Name.Substring (i+1));
238                                                 sb.Insert (0,'>');
239                                                 for (;nt > na; nt--) {
240                                                         sb.Insert (0, ToCSharpName (args[nt],full));
241                                                         if (nt - 1 != na)
242                                                                 sb.Insert (0, ',');
243                                                 }
244                                                 sb.Insert (0,'<');
245                                                 sb.Insert (0, pt.Name.Substring (0, i));
246                                         } else
247                                                 sb.Insert (0, pt.Name);
248                                         pt = pt.DeclaringType;
249                                 }
250                                 if (full && type.Namespace.Length > 0)
251                                         sb.Insert (0, type.Namespace + ".");
252                                 return sb.ToString ();
253                         }
254 #endif
255                         if (type.DeclaringType != null) {
256                                 sb.Append (ToCSharpName (type.DeclaringType, full)).Append ('.');
257                                 sb.Append (type.Name);
258                         }
259                         else {
260                                 if (full && type.Namespace.Length > 0)
261                                         sb.Append (type.Namespace).Append ('.');
262                                 sb.Append (type.Name);
263                         }
264                         return sb.ToString ();
265                 }
266                 
267                 static bool IsKeyword (string name)
268                 {
269                         if (keywordsTable == null) {
270                                 Hashtable t = new Hashtable ();
271                                 foreach (string s in keywords)
272                                         t [s] = s;
273                                 keywordsTable = t;
274                         }
275                         return keywordsTable.Contains (name);
276                 }
277
278                 public SchemaTypes SchemaType
279                 {
280                         get {
281                                 return sType;
282                         }
283                 }
284
285                 public bool IsListType
286                 {
287                         get { return SchemaType == SchemaTypes.Array; }
288                 }
289
290                 public bool IsComplexType
291                 {
292                         get 
293                         { 
294                                 return (SchemaType == SchemaTypes.Class || 
295                                               SchemaType == SchemaTypes.Array ||
296                                               SchemaType == SchemaTypes.Enum ||
297                                               SchemaType == SchemaTypes.XmlNode ||
298                                                   SchemaType == SchemaTypes.XmlSerializable ||
299                                                   !IsXsdType); 
300                         }
301                 }
302
303                 public bool IsValueType
304                 {
305                         get
306                         {
307                                 if (type != null) return type.IsValueType;
308                                 else return (sType == SchemaTypes.Primitive || sType == SchemaTypes.Enum);
309                         }
310                 }
311
312                 public bool NullableOverride
313                 {
314                         get { return nullableOverride; }
315                 }
316
317                 public bool IsNullable
318                 {
319                         get
320                         {
321                                 if (nullableOverride)
322                                         return true;
323 #if NET_2_0
324                                 return !IsValueType ||
325                                         (type != null &&
326                                          type.IsGenericType &&
327                                          type.GetGenericTypeDefinition () == typeof (Nullable<>));
328 #else
329                                 return !IsValueType;
330 #endif
331                         }
332
333                         set
334                         {
335                                 nullableOverride = value;
336                         }
337                 }
338
339                 public TypeData ListItemTypeData
340                 {
341                         get
342                         {
343                                 if (listItemTypeData == null && type != null)
344                                         listItemTypeData = TypeTranslator.GetTypeData (ListItemType);
345                                 return listItemTypeData;
346                         }
347                 }
348                 
349                 public Type ListItemType
350                 {
351                         get
352                         {
353                                 if (type == null) 
354                                         throw new InvalidOperationException ("Property ListItemType is not supported for custom types");
355
356                                 if (listItemType != null) return listItemType;
357
358                                 Type genericArgument = null;
359
360                                 if (SchemaType != SchemaTypes.Array)
361                                         throw new InvalidOperationException (Type.FullName + " is not a collection");
362                                 else if (type.IsArray) 
363                                         listItemType = type.GetElementType ();
364 #if NET_2_0
365                                 else if (typeof (ICollection).IsAssignableFrom (type) || (genericArgument = GetGenericListItemType (type)) != null)
366 #else
367                                 else if (typeof (ICollection).IsAssignableFrom (type))
368 #endif
369                                 {
370                                         if (typeof (IDictionary).IsAssignableFrom (type))
371                                                 throw new NotSupportedException (string.Format (CultureInfo.InvariantCulture,
372                                                         "The type {0} is not supported because it implements" +
373                                                         " IDictionary.", type.FullName));
374
375                                         if (genericArgument != null)
376                                                 listItemType = genericArgument;
377                                         else {
378                                                 PropertyInfo prop = GetIndexerProperty (type);
379                                                 if (prop == null) 
380                                                         throw new InvalidOperationException ("You must implement a default accessor on " + type.FullName + " because it inherits from ICollection");
381                                                 listItemType = prop.PropertyType;
382                                         }
383
384                                         MethodInfo addMethod = type.GetMethod ("Add", new Type[] { listItemType });
385                                         if (addMethod == null)
386                                                 throw CreateMissingAddMethodException (type, "ICollection",
387                                                         listItemType);
388                                 }
389                                 else // at this point, we must be dealing with IEnumerable implementation
390                                 {
391                                         MethodInfo met = type.GetMethod ("GetEnumerator", Type.EmptyTypes);
392                                         if (met == null) { 
393                                                 // get private implemenation
394                                                 met = type.GetMethod ("System.Collections.IEnumerable.GetEnumerator",
395                                                         BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
396                                                         null, Type.EmptyTypes, null);
397                                         }
398                                         // determine ListItemType using IEnumerator.Current property
399                                         PropertyInfo prop = met.ReturnType.GetProperty ("Current");
400                                         if (prop == null)
401                                                 listItemType = typeof (object);
402                                         else
403                                                 listItemType = prop.PropertyType;
404
405                                         MethodInfo addMethod = type.GetMethod ("Add", new Type[] { listItemType });
406                                         if (addMethod == null)
407                                                 throw CreateMissingAddMethodException (type, "IEnumerable",
408                                                         listItemType);
409                                 }
410
411                                 return listItemType;
412                         }
413                 }
414
415                 public TypeData ListTypeData
416                 {
417                         get
418                         {
419                                 if (listTypeData != null) return listTypeData;
420                                 
421                                 listTypeData = new TypeData (TypeName + "[]",
422                                         FullTypeName + "[]",
423                                         TypeTranslator.GetArrayName(XmlType),
424                                         SchemaTypes.Array, this);
425
426                                 return listTypeData;
427                         }
428                 }
429
430                 public bool IsXsdType {
431                         get { return mappedType == null; }
432                 }
433
434                 public TypeData MappedType {
435                         get {
436                                 return mappedType != null ? mappedType : this;
437                         }
438                 }
439
440                 public XmlSchemaPatternFacet XmlSchemaPatternFacet {
441                         get {
442                                 return facet;
443                         }
444                 }
445                 
446                 public bool HasPublicConstructor
447                 {
448                         get { return hasPublicConstructor; }
449                 }
450
451
452                 public static PropertyInfo GetIndexerProperty (Type collectionType)
453                 {
454                         PropertyInfo[] props = collectionType.GetProperties (BindingFlags.Instance | BindingFlags.Public);
455                         foreach (PropertyInfo prop in props)
456                         {
457                                 ParameterInfo[] pi = prop.GetIndexParameters ();
458                                 if (pi != null && pi.Length == 1 && pi[0].ParameterType == typeof(int))
459                                         return prop;
460                         }
461                         return null;
462                 }
463
464                 private static InvalidOperationException CreateMissingAddMethodException (Type type, string inheritFrom, Type argumentType) {
465                         return new InvalidOperationException (string.Format(CultureInfo.InvariantCulture,
466                                 "To be XML serializable, types which inherit from {0} must have " +
467                                 "an implementation of Add({1}) at all levels of their inheritance " +
468                                 "hierarchy. {2} does not implement Add({1}).", inheritFrom, 
469                                 argumentType.FullName, type.FullName));
470                 }
471
472                 private static Hashtable keywordsTable;
473                 private static string[] keywords = new string[] {
474                         "abstract","event","new","struct","as","explicit","null","switch","base","extern",
475                         "this","false","operator","throw","break","finally","out","true",
476                         "fixed","override","try","case","params","typeof","catch","for",
477                         "private","foreach","protected","checked","goto","public",
478                         "unchecked","class","if","readonly","unsafe","const","implicit","ref",
479                         "continue","in","return","using","virtual","default",
480                         "interface","sealed","volatile","delegate","internal","do","is",
481                         "sizeof","while","lock","stackalloc","else","static","enum",
482                         "namespace",
483                         "object","bool","byte","float","uint","char","ulong","ushort",
484                         "decimal","int","sbyte","short","double","long","string","void",
485 #if NET_2_0
486                         "partial", "yield", "where"
487 #endif
488                 };
489
490 #if NET_2_0
491                 internal static Type GetGenericListItemType (Type type)
492                 {
493                         if (type.IsGenericType && typeof(IEnumerable).IsAssignableFrom(type.GetGenericTypeDefinition ())) {
494                                 Type [] gatypes = type.GetGenericArguments ();
495                                 if (type.GetMethod ("Add", gatypes) != null)
496                                         return gatypes [0];
497                         }
498                         Type t = null;
499                         foreach (Type i in type.GetInterfaces ())
500                                 if ((t = GetGenericListItemType (i)) != null)
501                                         return t;
502                         return null;
503                 }
504 #endif
505         }
506 }