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