Fix code generation issue with nested generic types
[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         internal class TypeData
46         {
47                 Type type;
48                 string elementName;
49                 SchemaTypes sType;
50                 Type listItemType;
51                 string typeName;
52                 string fullTypeName;
53                 string csharpName;
54                 string csharpFullName;
55                 TypeData listItemTypeData;
56                 TypeData listTypeData;
57                 TypeData mappedType;
58                 XmlSchemaPatternFacet facet;
59                 bool hasPublicConstructor = true;
60                 bool nullableOverride;
61
62                 public TypeData (Type type, string elementName, bool isPrimitive) :
63                         this(type, elementName, isPrimitive, null, null) {}
64
65                 public TypeData (Type type, string elementName, bool isPrimitive, TypeData mappedType, XmlSchemaPatternFacet facet)
66                 {
67 #if NET_2_0
68                         if (type.IsGenericTypeDefinition)
69                                 throw new InvalidOperationException ("Generic type definition cannot be used in serialization. Only specific generic types can be used.");
70 #endif
71                         this.mappedType = mappedType;
72                         this.facet = facet;
73                         this.type = type;
74                         this.typeName = type.Name;
75                         this.fullTypeName = type.FullName.Replace ('+', '.');
76
77                         if (isPrimitive)
78                                 sType = SchemaTypes.Primitive;
79                         else
80                         {
81                                 if (type.IsEnum)
82                                         sType = SchemaTypes.Enum;
83                                 else if (typeof(IXmlSerializable).IsAssignableFrom (type))
84                                         sType = SchemaTypes.XmlSerializable;
85                                 else if (typeof (System.Xml.XmlNode).IsAssignableFrom (type))
86                                         sType = SchemaTypes.XmlNode;
87                                 else if (type.IsArray || typeof(IEnumerable).IsAssignableFrom (type))
88                                         sType = SchemaTypes.Array;
89                                 else
90                                         sType = SchemaTypes.Class;
91                         }
92                         
93                         if (IsListType)
94                                 this.elementName = TypeTranslator.GetArrayName (ListItemTypeData.XmlType);
95                         else
96                                 this.elementName = elementName;
97
98                         if (sType == SchemaTypes.Array || sType == SchemaTypes.Class) {
99                                 hasPublicConstructor = !type.IsInterface && (type.IsArray || type.GetConstructor (Type.EmptyTypes) != null || type.IsAbstract || type.IsValueType);
100                         }
101                 }
102
103                 internal TypeData (string typeName, string fullTypeName, string xmlType, SchemaTypes schemaType, TypeData listItemTypeData)
104                 {
105                         this.elementName = xmlType;
106                         this.typeName = typeName;
107                         this.fullTypeName = fullTypeName.Replace ('+', '.');
108                         this.listItemTypeData = listItemTypeData;
109                         this.sType = schemaType;
110                         this.hasPublicConstructor = true;
111                 }
112
113                 public string TypeName
114                 {
115                         get {
116                                 return typeName;
117                         }
118                 }
119                                 
120                 public string XmlType
121                 {
122                         get {
123                                 return elementName;
124                         }
125                 }
126                                 
127                 public Type Type
128                 {
129                         get {
130                                 return type;
131                         }
132                 }
133                                 
134                 public string FullTypeName
135                 {
136                         get {
137                                 return fullTypeName;
138                         }
139                 }
140
141                 public string CSharpName
142                 {
143                         get {
144                                 if (csharpName == null)
145                                         csharpName = (Type == null) ? TypeName : ToCSharpName (Type, false);
146                                 return csharpName;
147                         }
148                 }
149
150                 public string CSharpFullName
151                 {
152                         get {
153                                 if (csharpFullName == null)
154                                         csharpFullName = (Type == null) ? TypeName : ToCSharpName (Type, true);
155                                 return csharpFullName;
156                         }
157                 }
158
159                 // static Microsoft.CSharp.CSharpCodeProvider csprovider =
160                 //      new Microsoft.CSharp.CSharpCodeProvider ();
161
162                 public static string ToCSharpName (Type type, bool full)
163                 {
164                         // return csprovider.GetTypeOutput (new System.CodeDom.CodeTypeReference (type));
165                         StringBuilder sb = new StringBuilder ();
166                         
167                         if (type.IsArray) {
168                                 sb.Append (ToCSharpName (type.GetElementType (), full));
169                                 sb.Append ('[');
170                                 int rank = type.GetArrayRank ();
171                                 for (int i = 1; i < rank; i++)
172                                         sb.Append (',');
173                                 sb.Append (']');
174                                 return sb.ToString ();
175                         }
176 #if NET_2_0
177                         // Generic nested types return the complete list of type arguments,
178                         // including type arguments for the declaring class. This requires
179                         // some special handling
180                         if (type.IsGenericType && !type.IsGenericTypeDefinition) {
181                                 Type[] args = type.GetGenericArguments ();
182                                 int nt = args.Length - 1;
183                                 Type pt = type;
184                                 // Loop throguh the declaring class chain, consuming type arguments for every
185                                 // generic class in the chain
186                                 while (pt != null) {
187                                         if (sb.Length > 0)
188                                                 sb.Insert (0, '.');
189                                         int i = pt.Name.IndexOf ('`');
190                                         if (i != -1) {
191                                                 int na = nt - int.Parse (pt.Name.Substring (i+1));
192                                                 sb.Insert (0,'>');
193                                                 for (;nt > na; nt--) {
194                                                         sb.Insert (0, ToCSharpName (args[nt],full));
195                                                         if (nt - 1 != na)
196                                                                 sb.Insert (0, ',');
197                                                 }
198                                                 sb.Insert (0,'<');
199                                                 sb.Insert (0, pt.Name.Substring (0, i));
200                                         } else
201                                                 sb.Insert (0, pt.Name);
202                                         pt = pt.DeclaringType;
203                                 }
204                                 if (full && type.Namespace.Length > 0)
205                                         sb.Insert (0, type.Namespace + ".");
206                                 return sb.ToString ();
207                         }
208 #endif
209                         if (type.DeclaringType != null) {
210                                 sb.Append (ToCSharpName (type.DeclaringType, full)).Append ('.');
211                                 sb.Append (type.Name);
212                         }
213                         else {
214                                 if (full && type.Namespace.Length > 0)
215                                         sb.Append (type.Namespace).Append ('.');
216                                 sb.Append (type.Name);
217                         }
218                         return sb.ToString ();
219                 }
220                 
221                 static bool IsKeyword (string name)
222                 {
223                         if (keywordsTable == null) {
224                                 Hashtable t = new Hashtable ();
225                                 foreach (string s in keywords)
226                                         t [s] = s;
227                                 keywordsTable = t;
228                         }
229                         return keywordsTable.Contains (name);
230                 }
231
232                 public SchemaTypes SchemaType
233                 {
234                         get {
235                                 return sType;
236                         }
237                 }
238
239                 public bool IsListType
240                 {
241                         get { return SchemaType == SchemaTypes.Array; }
242                 }
243
244                 public bool IsComplexType
245                 {
246                         get 
247                         { 
248                                 return (SchemaType == SchemaTypes.Class || 
249                                               SchemaType == SchemaTypes.Array ||
250                                               SchemaType == SchemaTypes.Enum ||
251                                               SchemaType == SchemaTypes.XmlNode ||
252                                                   SchemaType == SchemaTypes.XmlSerializable ||
253                                                   !IsXsdType); 
254                         }
255                 }
256
257                 public bool IsValueType
258                 {
259                         get
260                         {
261                                 if (type != null) return type.IsValueType;
262                                 else return (sType == SchemaTypes.Primitive || sType == SchemaTypes.Enum);
263                         }
264                 }
265
266                 public bool NullableOverride
267                 {
268                         get { return nullableOverride; }
269                 }
270
271                 public bool IsNullable
272                 {
273                         get
274                         {
275                                 if (nullableOverride)
276                                         return true;
277 #if NET_2_0
278                                 return !IsValueType ||
279                                         (type != null &&
280                                          type.IsGenericType &&
281                                          type.GetGenericTypeDefinition () == typeof (Nullable<>));
282 #else
283                                 return !IsValueType;
284 #endif
285                         }
286
287                         set
288                         {
289                                 nullableOverride = value;
290                         }
291                 }
292
293                 public TypeData ListItemTypeData
294                 {
295                         get
296                         {
297                                 if (listItemTypeData == null && type != null)
298                                         listItemTypeData = TypeTranslator.GetTypeData (ListItemType);
299                                 return listItemTypeData;
300                         }
301                 }
302                 
303                 public Type ListItemType
304                 {
305                         get
306                         {
307                                 if (type == null) 
308                                         throw new InvalidOperationException ("Property ListItemType is not supported for custom types");
309
310                                 if (listItemType != null) return listItemType;
311
312                                 Type genericArgument = null;
313
314                                 if (SchemaType != SchemaTypes.Array)
315                                         throw new InvalidOperationException (Type.FullName + " is not a collection");
316                                 else if (type.IsArray) 
317                                         listItemType = type.GetElementType ();
318 #if NET_2_0
319                                 else if (typeof (ICollection).IsAssignableFrom (type) || (genericArgument = GetGenericListItemType (type)) != null)
320 #else
321                                 else if (typeof (ICollection).IsAssignableFrom (type))
322 #endif
323                                 {
324                                         if (typeof (IDictionary).IsAssignableFrom (type))
325                                                 throw new NotSupportedException (string.Format (CultureInfo.InvariantCulture,
326                                                         "The type {0} is not supported because it implements" +
327                                                         " IDictionary.", type.FullName));
328
329                                         if (genericArgument != null)
330                                                 listItemType = genericArgument;
331                                         else {
332                                                 PropertyInfo prop = GetIndexerProperty (type);
333                                                 if (prop == null) 
334                                                         throw new InvalidOperationException ("You must implement a default accessor on " + type.FullName + " because it inherits from ICollection");
335                                                 listItemType = prop.PropertyType;
336                                         }
337
338                                         MethodInfo addMethod = type.GetMethod ("Add", new Type[] { listItemType });
339                                         if (addMethod == null)
340                                                 throw CreateMissingAddMethodException (type, "ICollection",
341                                                         listItemType);
342                                 }
343                                 else // at this point, we must be dealing with IEnumerable implementation
344                                 {
345                                         MethodInfo met = type.GetMethod ("GetEnumerator", Type.EmptyTypes);
346                                         if (met == null) { 
347                                                 // get private implemenation
348                                                 met = type.GetMethod ("System.Collections.IEnumerable.GetEnumerator",
349                                                         BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
350                                                         null, Type.EmptyTypes, null);
351                                         }
352                                         // determine ListItemType using IEnumerator.Current property
353                                         PropertyInfo prop = met.ReturnType.GetProperty ("Current");
354                                         if (prop == null)
355                                                 listItemType = typeof (object);
356                                         else
357                                                 listItemType = prop.PropertyType;
358
359                                         MethodInfo addMethod = type.GetMethod ("Add", new Type[] { listItemType });
360                                         if (addMethod == null)
361                                                 throw CreateMissingAddMethodException (type, "IEnumerable",
362                                                         listItemType);
363                                 }
364
365                                 return listItemType;
366                         }
367                 }
368
369                 public TypeData ListTypeData
370                 {
371                         get
372                         {
373                                 if (listTypeData != null) return listTypeData;
374                                 
375                                 listTypeData = new TypeData (TypeName + "[]",
376                                         FullTypeName + "[]",
377                                         TypeTranslator.GetArrayName(XmlType),
378                                         SchemaTypes.Array, this);
379
380                                 return listTypeData;
381                         }
382                 }
383
384                 public bool IsXsdType {
385                         get { return mappedType == null; }
386                 }
387
388                 public TypeData MappedType {
389                         get {
390                                 return mappedType != null ? mappedType : this;
391                         }
392                 }
393
394                 public XmlSchemaPatternFacet XmlSchemaPatternFacet {
395                         get {
396                                 return facet;
397                         }
398                 }
399                 
400                 public bool HasPublicConstructor
401                 {
402                         get { return hasPublicConstructor; }
403                 }
404
405
406                 public static PropertyInfo GetIndexerProperty (Type collectionType)
407                 {
408                         PropertyInfo[] props = collectionType.GetProperties (BindingFlags.Instance | BindingFlags.Public);
409                         foreach (PropertyInfo prop in props)
410                         {
411                                 ParameterInfo[] pi = prop.GetIndexParameters ();
412                                 if (pi != null && pi.Length == 1 && pi[0].ParameterType == typeof(int))
413                                         return prop;
414                         }
415                         return null;
416                 }
417
418                 private static InvalidOperationException CreateMissingAddMethodException (Type type, string inheritFrom, Type argumentType) {
419                         return new InvalidOperationException (string.Format(CultureInfo.InvariantCulture,
420                                 "To be XML serializable, types which inherit from {0} must have " +
421                                 "an implementation of Add({1}) at all levels of their inheritance " +
422                                 "hierarchy. {2} does not implement Add({1}).", inheritFrom, 
423                                 argumentType.FullName, type.FullName));
424                 }
425
426                 private static Hashtable keywordsTable;
427                 private static string[] keywords = new string[] {
428                         "abstract","event","new","struct","as","explicit","null","switch","base","extern",
429                         "this","false","operator","throw","break","finally","out","true",
430                         "fixed","override","try","case","params","typeof","catch","for",
431                         "private","foreach","protected","checked","goto","public",
432                         "unchecked","class","if","readonly","unsafe","const","implicit","ref",
433                         "continue","in","return","using","virtual","default",
434                         "interface","sealed","volatile","delegate","internal","do","is",
435                         "sizeof","while","lock","stackalloc","else","static","enum",
436                         "namespace",
437                         "object","bool","byte","float","uint","char","ulong","ushort",
438                         "decimal","int","sbyte","short","double","long","string","void",
439 #if NET_2_0
440                         "partial", "yield", "where"
441 #endif
442                 };
443
444 #if NET_2_0
445                 private Type GetGenericListItemType (Type type)
446                 {
447                         if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (ICollection<>))
448                                 return type.GetGenericArguments () [0];
449                         Type t = null;
450                         foreach (Type i in type.GetInterfaces ())
451                                 if ((t = GetGenericListItemType (i)) != null)
452                                         return t;
453                         return null;
454                 }
455 #endif
456         }
457 }