2007-02-19 Atsushi Enomoto <atsushi@ximian.com>
[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 using System.Globalization;
36 using System.Reflection;
37 using System.Text;
38 using System.Xml.Schema;
39
40 namespace System.Xml.Serialization
41 {
42         internal class TypeData
43         {
44                 Type type;
45                 string elementName;
46                 SchemaTypes sType;
47                 Type listItemType;
48                 string typeName;
49                 string fullTypeName;
50                 string csharpName;
51                 string csharpFullName;
52                 TypeData listItemTypeData;
53                 TypeData listTypeData;
54                 TypeData mappedType;
55                 XmlSchemaPatternFacet facet;
56                 bool hasPublicConstructor = true;
57                 bool nullableOverride;
58
59                 public TypeData (Type type, string elementName, bool isPrimitive) :
60                         this(type, elementName, isPrimitive, null, null) {}
61
62                 public TypeData (Type type, string elementName, bool isPrimitive, TypeData mappedType, XmlSchemaPatternFacet facet)
63                 {
64 #if NET_2_0
65                         if (type.IsGenericTypeDefinition)
66                                 throw new InvalidOperationException ("Generic type definition cannot be used in serialization. Only specific generic types can be used.");
67 #endif
68                         this.mappedType = mappedType;
69                         this.facet = facet;
70                         this.type = type;
71                         this.typeName = type.Name;
72                         this.fullTypeName = type.FullName.Replace ('+', '.');
73
74                         if (isPrimitive)
75                                 sType = SchemaTypes.Primitive;
76                         else
77                         {
78                                 if (type.IsEnum)
79                                         sType = SchemaTypes.Enum;
80                                 else if (typeof(IXmlSerializable).IsAssignableFrom (type))
81                                         sType = SchemaTypes.XmlSerializable;
82                                 else if (typeof (System.Xml.XmlNode).IsAssignableFrom (type))
83                                         sType = SchemaTypes.XmlNode;
84                                 else if (type.IsArray || typeof(IEnumerable).IsAssignableFrom (type))
85                                         sType = SchemaTypes.Array;
86                                 else
87                                         sType = SchemaTypes.Class;
88                         }
89                         
90                         if (IsListType)
91                                 this.elementName = TypeTranslator.GetArrayName (ListItemTypeData.XmlType);
92                         else
93                                 this.elementName = elementName;
94
95                         if (sType == SchemaTypes.Array || sType == SchemaTypes.Class) {
96                                 hasPublicConstructor = !type.IsInterface && (type.IsArray || type.GetConstructor (Type.EmptyTypes) != null || type.IsAbstract || type.IsValueType);
97                         }
98                 }
99
100                 internal TypeData (string typeName, string fullTypeName, string xmlType, SchemaTypes schemaType, TypeData listItemTypeData)
101                 {
102                         this.elementName = xmlType;
103                         this.typeName = typeName;
104                         this.fullTypeName = fullTypeName.Replace ('+', '.');
105                         this.listItemTypeData = listItemTypeData;
106                         this.sType = schemaType;
107                         this.hasPublicConstructor = true;
108                 }
109
110                 public string TypeName
111                 {
112                         get {
113                                 return typeName;
114                         }
115                 }
116                                 
117                 public string XmlType
118                 {
119                         get {
120                                 return elementName;
121                         }
122                 }
123                                 
124                 public Type Type
125                 {
126                         get {
127                                 return type;
128                         }
129                 }
130                                 
131                 public string FullTypeName
132                 {
133                         get {
134                                 return fullTypeName;
135                         }
136                 }
137
138                 public string CSharpName
139                 {
140                         get {
141                                 if (csharpName == null)
142                                         csharpName = (Type == null) ? TypeName : ToCSharpName (Type, false);
143                                 return csharpName;
144                         }
145                 }
146
147                 public string CSharpFullName
148                 {
149                         get {
150                                 if (csharpFullName == null)
151                                         csharpFullName = (Type == null) ? TypeName : ToCSharpName (Type, true);
152                                 return csharpFullName;
153                         }
154                 }
155
156                 // static Microsoft.CSharp.CSharpCodeProvider csprovider =
157                 //      new Microsoft.CSharp.CSharpCodeProvider ();
158
159                 public static string ToCSharpName (Type type, bool full)
160                 {
161                         // return csprovider.GetTypeOutput (new System.CodeDom.CodeTypeReference (type));
162                         if (type.IsArray) {
163                                 StringBuilder sb = new StringBuilder ();
164                                 sb.Append (ToCSharpName (type.GetElementType (), full));
165                                 sb.Append ('[');
166                                 int rank = type.GetArrayRank ();
167                                 for (int i = 1; i < rank; i++)
168                                         sb.Append (',');
169                                 sb.Append (']');
170                                 return sb.ToString ();
171                         }
172 #if NET_2_0
173                         if (type.IsGenericType && !type.IsGenericTypeDefinition) {
174                                 StringBuilder sb = new StringBuilder ();
175                                 sb.Append (ToCSharpName (type.GetGenericTypeDefinition (), full));
176                                 sb.Append ('<');
177                                 foreach (Type arg in type.GetGenericArguments ())
178                                         sb.Append (ToCSharpName (arg, full)).Append (',');
179                                 sb.Length--;
180                                 sb.Append ('>');
181                                 return sb.ToString ();
182                         }
183 #endif
184                         string name = full ? type.FullName : type.Name;
185                         name = name.Replace ('+', '.');
186                         int idx = name.IndexOf ('`'); // generic definition has extra `n.
187                         return idx > 0 ? name.Substring (0, idx) : name;
188                 }
189
190                 public SchemaTypes SchemaType
191                 {
192                         get {
193                                 return sType;
194                         }
195                 }
196
197                 public bool IsListType
198                 {
199                         get { return SchemaType == SchemaTypes.Array; }
200                 }
201
202                 public bool IsComplexType
203                 {
204                         get 
205                         { 
206                                 return (SchemaType == SchemaTypes.Class || 
207                                               SchemaType == SchemaTypes.Array ||
208                                               SchemaType == SchemaTypes.Enum ||
209                                               SchemaType == SchemaTypes.XmlNode ||
210                                                   SchemaType == SchemaTypes.XmlSerializable ||
211                                                   !IsXsdType); 
212                         }
213                 }
214
215                 public bool IsValueType
216                 {
217                         get
218                         {
219                                 if (type != null) return type.IsValueType;
220                                 else return (sType == SchemaTypes.Primitive || sType == SchemaTypes.Enum);
221                         }
222                 }
223
224                 public bool IsNullable
225                 {
226                         get
227                         {
228                                 if (nullableOverride)
229                                         return true;
230 #if NET_2_0
231                                 return !IsValueType ||
232                                         (type != null &&
233                                          type.IsGenericType &&
234                                          type.GetGenericTypeDefinition () == typeof (Nullable<>));
235 #else
236                                 return !IsValueType;
237 #endif
238                         }
239
240                         set
241                         {
242                                 nullableOverride = true;
243                         }
244                 }
245
246                 public TypeData ListItemTypeData
247                 {
248                         get
249                         {
250                                 if (listItemTypeData == null && type != null)
251                                         listItemTypeData = TypeTranslator.GetTypeData (ListItemType);
252                                 return listItemTypeData;
253                         }
254                 }
255                 
256                 public Type ListItemType
257                 {
258                         get
259                         {
260                                 if (type == null) 
261                                         throw new InvalidOperationException ("Property ListItemType is not supported for custom types");
262
263                                 if (listItemType != null) return listItemType;
264
265                                 if (SchemaType != SchemaTypes.Array)
266                                         throw new InvalidOperationException (Type.FullName + " is not a collection");
267                                 else if (type.IsArray) 
268                                         listItemType = type.GetElementType ();
269                                 else if (typeof(ICollection).IsAssignableFrom (type))
270                                 {
271                                         if (typeof (IDictionary).IsAssignableFrom (type))
272                                                 throw new NotSupportedException (string.Format (CultureInfo.InvariantCulture,
273                                                         "The type {0} is not supported because it implements" +
274                                                         " IDictionary.", type.FullName));
275
276                                         PropertyInfo prop = GetIndexerProperty (type);
277                                         if (prop == null) 
278                                                 throw new InvalidOperationException ("You must implement a default accessor on " + type.FullName + " because it inherits from ICollection");
279
280                                         listItemType = prop.PropertyType;
281
282                                         MethodInfo addMethod = type.GetMethod ("Add", new Type[] { listItemType });
283                                         if (addMethod == null)
284                                                 throw CreateMissingAddMethodException (type, "ICollection",
285                                                         listItemType);
286                                 }
287                                 else // at this point, we must be dealing with IEnumerable implementation
288                                 {
289                                         MethodInfo met = type.GetMethod ("GetEnumerator", Type.EmptyTypes);
290                                         if (met == null) { 
291                                                 // get private implemenation
292                                                 met = type.GetMethod ("System.Collections.IEnumerable.GetEnumerator",
293                                                         BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
294                                                         null, Type.EmptyTypes, null);
295                                         }
296                                         // determine ListItemType using IEnumerator.Current property
297                                         PropertyInfo prop = met.ReturnType.GetProperty ("Current");
298                                         if (prop == null)
299                                                 listItemType = typeof (object);
300                                         else
301                                                 listItemType = prop.PropertyType;
302
303                                         MethodInfo addMethod = type.GetMethod ("Add", new Type[] { listItemType });
304                                         if (addMethod == null)
305                                                 throw CreateMissingAddMethodException (type, "IEnumerable",
306                                                         listItemType);
307                                 }
308
309                                 return listItemType;
310                         }
311                 }
312
313                 public TypeData ListTypeData
314                 {
315                         get
316                         {
317                                 if (listTypeData != null) return listTypeData;
318                                 
319                                 listTypeData = new TypeData (TypeName + "[]",
320                                         FullTypeName + "[]",
321                                         TypeTranslator.GetArrayName(XmlType),
322                                         SchemaTypes.Array, this);
323
324                                 return listTypeData;
325                         }
326                 }
327
328                 public bool IsXsdType {
329                         get { return mappedType == null; }
330                 }
331
332                 public TypeData MappedType {
333                         get {
334                                 return mappedType != null ? mappedType : this;
335                         }
336                 }
337
338                 public XmlSchemaPatternFacet XmlSchemaPatternFacet {
339                         get {
340                                 return facet;
341                         }
342                 }
343                 
344                 public bool HasPublicConstructor
345                 {
346                         get { return hasPublicConstructor; }
347                 }
348
349
350                 public static PropertyInfo GetIndexerProperty (Type collectionType)
351                 {
352                         PropertyInfo[] props = collectionType.GetProperties (BindingFlags.Instance | BindingFlags.Public);
353                         foreach (PropertyInfo prop in props)
354                         {
355                                 ParameterInfo[] pi = prop.GetIndexParameters ();
356                                 if (pi != null && pi.Length == 1 && pi[0].ParameterType == typeof(int))
357                                         return prop;
358                         }
359                         return null;
360                 }
361
362                 private static InvalidOperationException CreateMissingAddMethodException (Type type, string inheritFrom, Type argumentType) {
363                         return new InvalidOperationException (string.Format(CultureInfo.InvariantCulture,
364                                 "To be XML serializable, types which inherit from {0} must have " +
365                                 "an implementation of Add({1}) at all levels of their inheritance " +
366                                 "hierarchy. {2} does not implement Add({1}).", inheritFrom, 
367                                 argumentType.FullName, type.FullName));
368                 }
369         }
370 }