Merge remote-tracking branch 'upstream/master'
[mono.git] / mcs / class / System.Runtime.Serialization / System.Runtime.Serialization / KnownTypeCollection.cs
1 //
2 // KnownTypeCollection.cs
3 //
4 // Author:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Copyright (C) 2005 Novell, Inc.  http://www.novell.com
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 #if NET_2_0
29 using System;
30 using System.Collections;
31 using System.Collections.Generic;
32 using System.Collections.ObjectModel;
33 using System.Linq;
34 using System.Reflection;
35 using System.Xml;
36 using System.Xml.Schema;
37
38 using QName = System.Xml.XmlQualifiedName;
39 using System.Xml.Serialization;
40
41 namespace System.Runtime.Serialization
42 {
43 /*
44         XmlFormatter implementation design inference:
45
46         type definitions:
47         - No XML Schema types are directly used. There are some maps from
48           xs:blahType to ms:blahType where the namespaceURI for prefix "ms" is
49           "http://schemas.microsoft.com/2003/10/Serialization/" .
50
51         serializable types:
52         - An object being serialized 1) must be of type System.Object, or
53           2) must be null, or 3) must have either a [DataContract] attribute
54           or a [Serializable] attribute to be serializable.
55         - When the object is either of type System.Object or null, then the
56           XML type is "anyType".
57         - When the object is [Serializable], then the runtime-serialization
58           compatible object graph is written.
59         - Otherwise the serialization is based on contract attributes.
60           ([Serializable] takes precedence).
61
62         type derivation:
63         - For type A to be serializable, the base type B of A must be
64           serializable.
65         - If a type which is [Serializable] and whose base type has a
66           [DataContract], then for base type members [DataContract] is taken.
67         - It is vice versa i.e. if the base type is [Serializable] and the
68           derived type has a [DataContract], then [Serializable] takes place
69           for base members.
70
71         known type collection:
72         - It internally manages mapping store keyed by contract QNames.
73           KnownTypeCollection.Add() checks if the same QName contract already
74           exists (and raises InvalidOperationException if required).
75
76 */
77
78         internal static class TypeExtensions
79         {
80 #if !NET_4_5
81                 public static T GetCustomAttribute<T> (this MemberInfo type, bool inherit)
82                 {
83                         var arr = type.GetCustomAttributes (typeof (T), inherit);
84                         return arr != null && arr.Length == 1 ? (T) arr [0] : default (T);
85                 }
86 #endif
87                 public static IEnumerable<Type> GetInterfacesOrSelfInterface (this Type type)
88                 {
89                         if (type.IsInterface)
90                                 yield return type;
91                         foreach (var t in type.GetInterfaces ())
92                                 yield return t;
93                 }
94
95                 public static bool ImplementsInterface (this Type type, Type iface)
96                 {
97                         foreach (var t in type.GetInterfacesOrSelfInterface ()) {
98                                 if (t == iface)
99                                         return true;
100                         }
101
102                         var baseType = type.BaseType;
103                         if (baseType != null)
104                                 return baseType.ImplementsInterface (iface);
105                         
106                         return false;
107                 }
108         }
109
110         internal sealed class KnownTypeCollection : Collection<Type>
111         {
112                 internal const string MSSimpleNamespace =
113                         "http://schemas.microsoft.com/2003/10/Serialization/";
114                 internal const string MSArraysNamespace =
115                         "http://schemas.microsoft.com/2003/10/Serialization/Arrays";
116                 internal const string DefaultClrNamespaceBase =
117                         "http://schemas.datacontract.org/2004/07/";
118                 internal const string DefaultClrNamespaceSystem =
119                         "http://schemas.datacontract.org/2004/07/System";
120
121
122                 static QName any_type, bool_type,
123                         byte_type, date_type, decimal_type, double_type,
124                         float_type, string_type,
125                         short_type, int_type, long_type,
126                         ubyte_type, ushort_type, uint_type, ulong_type,
127                         // non-TypeCode
128                         any_uri_type, base64_type, duration_type, qname_type,
129                         // custom in ms nsURI schema
130                         char_type, guid_type,
131                         // not in ms nsURI schema
132                         dbnull_type, date_time_offset_type;
133
134                 // XmlSchemaType.GetBuiltInPrimitiveType() does not exist in moonlight, so I had to explicitly add them. And now that we have it, it does not make much sense to use #if MOONLIGHT ... #endif for XmlSchemaType anymore :-(
135                 static Dictionary<string,Type> xs_predefined_types = new Dictionary<string,Type> ();
136
137                 static KnownTypeCollection ()
138                 {
139                         string s = MSSimpleNamespace;
140                         any_type = new QName ("anyType", s);
141                         any_uri_type = new QName ("anyURI", s);
142                         bool_type = new QName ("boolean", s);
143                         base64_type = new QName ("base64Binary", s);
144                         date_type = new QName ("dateTime", s);
145                         duration_type = new QName ("duration", s);
146                         qname_type = new QName ("QName", s);
147                         decimal_type = new QName ("decimal", s);
148                         double_type = new QName ("double", s);
149                         float_type = new QName ("float", s);
150                         byte_type = new QName ("byte", s);
151                         short_type = new QName ("short", s);
152                         int_type = new QName ("int", s);
153                         long_type = new QName ("long", s);
154                         ubyte_type = new QName ("unsignedByte", s);
155                         ushort_type = new QName ("unsignedShort", s);
156                         uint_type = new QName ("unsignedInt", s);
157                         ulong_type = new QName ("unsignedLong", s);
158                         string_type = new QName ("string", s);
159                         guid_type = new QName ("guid", s);
160                         char_type = new QName ("char", s);
161
162                         dbnull_type = new QName ("DBNull", DefaultClrNamespaceBase + "System");
163                         date_time_offset_type = new QName ("DateTimeOffset", DefaultClrNamespaceBase + "System");
164
165                         xs_predefined_types.Add ("string", typeof (string));
166                         xs_predefined_types.Add ("boolean", typeof (bool));
167                         xs_predefined_types.Add ("float", typeof (float));
168                         xs_predefined_types.Add ("double", typeof (double));
169                         xs_predefined_types.Add ("decimal", typeof (decimal));
170                         xs_predefined_types.Add ("duration", typeof (TimeSpan));
171                         xs_predefined_types.Add ("dateTime", typeof (DateTime));
172                         xs_predefined_types.Add ("date", typeof (DateTime));
173                         xs_predefined_types.Add ("time", typeof (DateTime));
174                         xs_predefined_types.Add ("gYearMonth", typeof (DateTime));
175                         xs_predefined_types.Add ("gYear", typeof (DateTime));
176                         xs_predefined_types.Add ("gMonthDay", typeof (DateTime));
177                         xs_predefined_types.Add ("gDay", typeof (DateTime));
178                         xs_predefined_types.Add ("gMonth", typeof (DateTime));
179                         xs_predefined_types.Add ("hexBinary", typeof (byte []));
180                         xs_predefined_types.Add ("base64Binary", typeof (byte []));
181                         xs_predefined_types.Add ("anyURI", typeof (Uri));
182                         xs_predefined_types.Add ("QName", typeof (QName));
183                         xs_predefined_types.Add ("NOTATION", typeof (string));
184
185                         xs_predefined_types.Add ("normalizedString", typeof (string));
186                         xs_predefined_types.Add ("token", typeof (string));
187                         xs_predefined_types.Add ("language", typeof (string));
188                         xs_predefined_types.Add ("IDREFS", typeof (string []));
189                         xs_predefined_types.Add ("ENTITIES", typeof (string []));
190                         xs_predefined_types.Add ("NMTOKEN", typeof (string));
191                         xs_predefined_types.Add ("NMTOKENS", typeof (string []));
192                         xs_predefined_types.Add ("Name", typeof (string));
193                         xs_predefined_types.Add ("NCName", typeof (string));
194                         xs_predefined_types.Add ("ID", typeof (string));
195                         xs_predefined_types.Add ("IDREF", typeof (string));
196                         xs_predefined_types.Add ("ENTITY", typeof (string));
197
198                         xs_predefined_types.Add ("integer", typeof (decimal));
199                         xs_predefined_types.Add ("nonPositiveInteger", typeof (int));
200                         xs_predefined_types.Add ("negativeInteger", typeof (int));
201                         xs_predefined_types.Add ("long", typeof (long));
202                         xs_predefined_types.Add ("int", typeof (int));
203                         xs_predefined_types.Add ("short", typeof (short));
204                         xs_predefined_types.Add ("byte", typeof (sbyte));
205                         xs_predefined_types.Add ("nonNegativeInteger", typeof (decimal));
206                         xs_predefined_types.Add ("unsignedLong", typeof (ulong));
207                         xs_predefined_types.Add ("unsignedInt", typeof (uint));
208                         xs_predefined_types.Add ("unsignedShort", typeof (ushort));
209                         xs_predefined_types.Add ("unsignedByte", typeof (byte));
210                         xs_predefined_types.Add ("positiveInteger", typeof (decimal));
211
212                         xs_predefined_types.Add ("anyType", typeof (object));
213                 }
214
215                 // FIXME: find out how QName and guid are processed
216
217                 internal QName GetXmlName (Type type)
218                 {
219                         SerializationMap map = FindUserMap (type);
220                         if (map != null)
221                                 return map.XmlName;
222                         return GetPredefinedTypeName (type);
223                 }
224
225                 internal static QName GetPredefinedTypeName (Type type)
226                 {
227                         QName name = GetPrimitiveTypeName (type);
228                         if (name != QName.Empty)
229                                 return name;
230                         if (type == typeof (DBNull))
231                                 return dbnull_type;
232                         return QName.Empty;
233                 }
234
235                 internal static QName GetPrimitiveTypeName (Type type)
236                 {
237                         if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>))
238                                 return GetPrimitiveTypeName (type.GetGenericArguments () [0]);
239
240                         if (type.IsEnum)
241                                 return QName.Empty;
242
243                         switch (Type.GetTypeCode (type)) {
244                         case TypeCode.Object: // other than System.Object
245                         case TypeCode.DBNull: // it is natively mapped, but not in ms serialization namespace.
246                         case TypeCode.Empty:
247                         default:
248                                 if (type == typeof (object))
249                                         return any_type;
250                                 if (type == typeof (Guid))
251                                         return guid_type;
252                                 if (type == typeof (TimeSpan))
253                                         return duration_type;
254                                 if (type == typeof (byte []))
255                                         return base64_type;
256                                 if (type == typeof (Uri))
257                                         return any_uri_type;
258                                 if (type == typeof (DateTimeOffset))
259                                         return date_time_offset_type;
260                                 return QName.Empty;
261                         case TypeCode.Boolean:
262                                 return bool_type;
263                         case TypeCode.Byte:
264                                 return ubyte_type;
265                         case TypeCode.Char:
266                                 return char_type;
267                         case TypeCode.DateTime:
268                                 return date_type;
269                         case TypeCode.Decimal:
270                                 return decimal_type;
271                         case TypeCode.Double:
272                                 return double_type;
273                         case TypeCode.Int16:
274                                 return short_type;
275                         case TypeCode.Int32:
276                                 return int_type;
277                         case TypeCode.Int64:
278                                 return long_type;
279                         case TypeCode.SByte:
280                                 return byte_type;
281                         case TypeCode.Single:
282                                 return float_type;
283                         case TypeCode.String:
284                                 return string_type;
285                         case TypeCode.UInt16:
286                                 return ushort_type;
287                         case TypeCode.UInt32:
288                                 return uint_type;
289                         case TypeCode.UInt64:
290                                 return ulong_type;
291                         }
292                 }
293
294                 internal static string PredefinedTypeObjectToString (object obj)
295                 {
296                         Type type = obj.GetType ();
297                         switch (Type.GetTypeCode (type)) {
298                         case TypeCode.Object: // other than System.Object
299                         case TypeCode.Empty:
300                         default:
301                                 if (type == typeof (object))
302                                         return String.Empty;
303                                 if (type == typeof (Guid))
304                                         return XmlConvert.ToString ((Guid) obj);
305                                 if (type == typeof (TimeSpan))
306                                         return XmlConvert.ToString ((TimeSpan) obj);
307                                 if (type == typeof (byte []))
308                                         return Convert.ToBase64String ((byte []) obj);
309                                 if (type == typeof (Uri))
310                                         return ((Uri) obj).ToString ();
311                                 throw new Exception ("Internal error: missing predefined type serialization for type " + type.FullName);
312                         case TypeCode.DBNull: // predefined, but not primitive
313                                 return String.Empty;
314                         case TypeCode.Boolean:
315                                 return XmlConvert.ToString ((bool) obj);
316                         case TypeCode.Byte:
317                                 return XmlConvert.ToString ((int)((byte) obj));
318                         case TypeCode.Char:
319                                 return XmlConvert.ToString ((uint) (char) obj);
320                         case TypeCode.DateTime:
321                                 return XmlConvert.ToString ((DateTime) obj, XmlDateTimeSerializationMode.RoundtripKind);
322                         case TypeCode.Decimal:
323                                 return XmlConvert.ToString ((decimal) obj);
324                         case TypeCode.Double:
325                                 return XmlConvert.ToString ((double) obj);
326                         case TypeCode.Int16:
327                                 return XmlConvert.ToString ((short) obj);
328                         case TypeCode.Int32:
329                                 return XmlConvert.ToString ((int) obj);
330                         case TypeCode.Int64:
331                                 return XmlConvert.ToString ((long) obj);
332                         case TypeCode.SByte:
333                                 return XmlConvert.ToString ((sbyte) obj);
334                         case TypeCode.Single:
335                                 return XmlConvert.ToString ((float) obj);
336                         case TypeCode.String:
337                                 return (string) obj;
338                         case TypeCode.UInt16:
339                                 return XmlConvert.ToString ((int) (ushort) obj);
340                         case TypeCode.UInt32:
341                                 return XmlConvert.ToString ((uint) obj);
342                         case TypeCode.UInt64:
343                                 return XmlConvert.ToString ((ulong) obj);
344                         }
345                 }
346
347                 internal static Type GetPrimitiveTypeFromName (QName name)
348                 {
349                         switch (name.Namespace) {
350                         case DefaultClrNamespaceSystem:
351                                 switch (name.Name) {
352                                 case "DBNull":
353                                         return typeof (DBNull);
354                                 case "DateTimeOffset":
355                                         return typeof (DateTimeOffset);
356                                 }
357                                 break;
358                         case XmlSchema.Namespace:
359                                 return xs_predefined_types.FirstOrDefault (p => p.Key == name.Name).Value;
360                         case MSSimpleNamespace:
361                                 switch (name.Name) {
362                                 case "anyURI":
363                                         return typeof (Uri);
364                                 case "boolean":
365                                         return typeof (bool);
366                                 case "base64Binary":
367                                         return typeof (byte []);
368                                 case "dateTime":
369                                         return typeof (DateTime);
370                                 case "duration":
371                                         return typeof (TimeSpan);
372                                 case "QName":
373                                         return typeof (QName);
374                                 case "decimal":
375                                         return typeof (decimal);
376                                 case "double":
377                                         return typeof (double);
378                                 case "float":
379                                         return typeof (float);
380                                 case "byte":
381                                         return typeof (sbyte);
382                                 case "short":
383                                         return typeof (short);
384                                 case "int":
385                                         return typeof (int);
386                                 case "long":
387                                         return typeof (long);
388                                 case "unsignedByte":
389                                         return typeof (byte);
390                                 case "unsignedShort":
391                                         return typeof (ushort);
392                                 case "unsignedInt":
393                                         return typeof (uint);
394                                 case "unsignedLong":
395                                         return typeof (ulong);
396                                 case "string":
397                                         return typeof (string);
398                                 case "anyType":
399                                         return typeof (object);
400                                 case "guid":
401                                         return typeof (Guid);
402                                 case "char":
403                                         return typeof (char);
404                                 }
405                                 break;
406                         }
407                         return null;
408                 }
409
410
411                 internal static object PredefinedTypeStringToObject (string s,
412                         string name, XmlReader reader)
413                 {
414                         switch (name) {
415                         case "anyURI":
416                                 return new Uri(s,UriKind.RelativeOrAbsolute);
417                         case "boolean":
418                                 return XmlConvert.ToBoolean (s);
419                         case "base64Binary":
420                                 return Convert.FromBase64String (s);
421                         case "dateTime":
422                                 return XmlConvert.ToDateTime (s, XmlDateTimeSerializationMode.RoundtripKind);
423                         case "duration":
424                                 return XmlConvert.ToTimeSpan (s);
425                         case "QName":
426                                 int idx = s.IndexOf (':');
427                                 string l = idx < 0 ? s : s.Substring (idx + 1);
428                                 return idx < 0 ? new QName (l) :
429                                         new QName (l, reader.LookupNamespace (
430                                                 s.Substring (0, idx)));
431                         case "decimal":
432                                 return XmlConvert.ToDecimal (s);
433                         case "double":
434                                 return XmlConvert.ToDouble (s);
435                         case "float":
436                                 return XmlConvert.ToSingle (s);
437                         case "byte":
438                                 return XmlConvert.ToSByte (s);
439                         case "short":
440                                 return XmlConvert.ToInt16 (s);
441                         case "int":
442                                 return XmlConvert.ToInt32 (s);
443                         case "long":
444                                 return XmlConvert.ToInt64 (s);
445                         case "unsignedByte":
446                                 return XmlConvert.ToByte (s);
447                         case "unsignedShort":
448                                 return XmlConvert.ToUInt16 (s);
449                         case "unsignedInt":
450                                 return XmlConvert.ToUInt32 (s);
451                         case "unsignedLong":
452                                 return XmlConvert.ToUInt64 (s);
453                         case "string":
454                                 return s;
455                         case "guid":
456                                 return XmlConvert.ToGuid (s);
457                         case "anyType":
458                                 return s;
459                         case "char":
460                                 return (char) XmlConvert.ToUInt32 (s);
461                         default:
462                                 throw new Exception ("Unanticipated primitive type: " + name);
463                         }
464                 }
465
466                 List<SerializationMap> contracts = new List<SerializationMap> ();
467
468                 public KnownTypeCollection ()
469                 {
470                 }
471
472                 protected override void ClearItems ()
473                 {
474                         base.Clear ();
475                 }
476
477                 protected override void InsertItem (int index, Type type)
478                 {
479                         if (ShouldNotRegister (type))
480                                 return;
481                         if (!Contains (type)) {
482                                 TryRegister (type);
483                                 base.InsertItem (index, type);
484                         }
485                 }
486
487                 // FIXME: it could remove other types' dependencies.
488                 protected override void RemoveItem (int index)
489                 {
490                         lock (this)
491                                 DoRemoveItem (index);
492                 }
493
494                 void DoRemoveItem (int index)
495                 {
496                         Type t = base [index];
497                         List<SerializationMap> l = new List<SerializationMap> ();
498                         foreach (SerializationMap m in contracts) {
499                                 if (m.RuntimeType == t)
500                                         l.Add (m);
501                         }
502                         foreach (SerializationMap m in l) {
503                                 contracts.Remove (m);
504                                 base.RemoveItem (index);
505                         }
506                 }
507
508                 protected override void SetItem (int index, Type type)
509                 {
510                         if (ShouldNotRegister (type))
511                                 return;
512
513                         // Since this collection is not assured to be ordered, it ignores the whole Set operation if the type already exists.
514                         if (Contains (type))
515                                 return;
516
517                         if (index != Count)
518                                 RemoveItem (index);
519                         if (TryRegister (type))
520                                 base.InsertItem (index - 1, type);
521                 }
522
523                 internal SerializationMap FindUserMap (Type type)
524                 {
525                         lock (this) {
526                                 for (int i = 0; i < contracts.Count; i++)
527                                         if (type == contracts [i].RuntimeType)
528                                                 return contracts [i];
529                                 return null;
530                         }
531                 }
532
533                 internal SerializationMap FindUserMap (QName qname)
534                 {
535                         lock (this)
536                                 return contracts.FirstOrDefault (c => c.XmlName == qname);
537                 }
538
539                 internal SerializationMap FindUserMap (QName qname, Type type)
540                 {
541                         lock (this)
542                                 return contracts.FirstOrDefault (c => c.XmlName == qname && c.RuntimeType == type);
543                 }
544
545                 internal Type GetSerializedType (Type type)
546                 {
547                         if (IsPrimitiveNotEnum (type))
548                                 return type;
549                         Type element = GetCollectionElementType (type);
550                         if (element == null)
551                                 return type;
552                         QName name = GetQName (type);
553                         var map = FindUserMap (name, type);
554                         if (map != null)
555                                 return map.RuntimeType;
556                         return type;
557                 }
558
559                 internal QName GetQName (Type type)
560                 {
561                         SerializationMap map = FindUserMap (type);
562                         if (map != null)
563                                 // already mapped.
564                                 return map.XmlName;
565                         return GetStaticQName (type);
566                 }
567
568                 public static QName GetStaticQName (Type type)
569                 {
570                         if (IsPrimitiveNotEnum (type))
571                                 return GetPrimitiveTypeName (type);
572
573                         if (type.IsEnum)
574                                 return GetEnumQName (type);
575
576                         QName qname = GetContractQName (type);
577                         if (qname != null)
578                                 return qname;
579
580                         if (type.GetInterface ("System.Xml.Serialization.IXmlSerializable") != null)
581                                 //FIXME: Reusing GetSerializableQName here, since we just
582                                 //need name of the type..
583                                 return GetSerializableQName (type);
584
585                         qname = GetCollectionContractQName (type);
586                         if (qname != null)
587                                 return qname;
588
589                         Type element = GetCollectionElementType (type);
590                         if (element != null) {
591                                 if (type.IsInterface || IsCustomCollectionType (type, element))
592                                         return GetCollectionQName (element);
593                         }
594
595                         if (GetAttribute<SerializableAttribute> (type) != null)
596                                 return GetSerializableQName (type);
597
598                         // default type map - still uses GetContractQName().
599                         return GetContractQName (type, null, null);
600                 }
601
602                 internal static QName GetContractQName (Type type)
603                 {
604                         var a = GetAttribute<DataContractAttribute> (type);
605                         return a == null ? null : GetContractQName (type, a.Name, a.Namespace);
606                 }
607
608                 static QName GetCollectionContractQName (Type type)
609                 {
610                         var a = GetAttribute<CollectionDataContractAttribute> (type);
611                         return a == null ? null : GetContractQName (type, a.Name, a.Namespace);
612                 }
613
614                 static QName GetContractQName (Type type, string name, string ns)
615                 {
616                         if (name == null)
617                                 name = GetDefaultName (type);
618                         else if (type.IsGenericType) {
619                                 var args = type.GetGenericArguments ();
620                                 for (int i = 0; i < args.Length; i++)
621                                         name = name.Replace ("{" + i + "}", GetStaticQName (args [i]).Name);
622                         }
623
624                         if (ns == null)
625                                 ns = GetDefaultNamespace (type);
626                         return new QName (name, ns);
627                 }
628
629                 static QName GetEnumQName (Type type)
630                 {
631                         string name = null, ns = null;
632
633                         if (!type.IsEnum)
634                                 return null;
635
636                         var dca = GetAttribute<DataContractAttribute> (type);
637
638                         if (dca != null) {
639                                 ns = dca.Namespace;
640                                 name = dca.Name;
641                         }
642
643                         if (ns == null)
644                                 ns = GetDefaultNamespace (type);
645
646                         if (name == null)
647                                 name = type.Namespace == null ? type.Name : type.FullName.Substring (type.Namespace.Length + 1).Replace ('+', '.');
648
649                         return new QName (name, ns);
650                 }
651
652                 internal static string GetDefaultName (Type type)
653                 {
654                         // FIXME: there could be decent ways to get
655                         // the same result...
656                         string name = type.Namespace == null || type.Namespace.Length == 0 ? type.Name : type.FullName.Substring (type.Namespace.Length + 1).Replace ('+', '.');
657                         if (type.IsGenericType) {
658                                 name = name.Substring (0, name.IndexOf ('`')) + "Of";
659                                 foreach (var t in type.GetGenericArguments ())
660                                         name += t.Name; // FIXME: check namespaces too
661                         }
662                         return name;
663                 }
664
665                 internal static string GetDefaultNamespace (Type type)
666                 {
667                         foreach (ContractNamespaceAttribute a in type.Assembly.GetCustomAttributes (typeof (ContractNamespaceAttribute), true))
668                                 if (a.ClrNamespace == type.Namespace)
669                                         return a.ContractNamespace;
670                         return DefaultClrNamespaceBase + type.Namespace;
671                 }
672
673                 static QName GetCollectionQName (Type element)
674                 {
675                         QName eqname = GetStaticQName (element);
676
677                         string ns = eqname.Namespace;
678                         if (eqname.Namespace == MSSimpleNamespace)
679                                 //Arrays of Primitive types
680                                 ns = MSArraysNamespace;
681
682                         return new QName (
683                                 "ArrayOf" + XmlConvert.EncodeLocalName (eqname.Name),
684                                 ns);
685                 }
686
687                 static QName GetSerializableQName (Type type)
688                 {
689                         // First, check XmlSchemaProviderAttribute and try GetSchema() to see if it returns a schema in the expected format.
690                         var xpa = type.GetCustomAttribute<XmlSchemaProviderAttribute> (true);
691                         if (xpa != null) {
692                                 if (xpa.IsAny)
693                                         return XmlQualifiedName.Empty;
694                                 var mi = type.GetMethod (xpa.MethodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
695                                 if (mi != null) {
696                                         try {
697                                                 var xss = new XmlSchemaSet ();
698                                                 return (XmlQualifiedName) mi.Invoke (null, new object [] {xss});
699                                         } catch {
700                                                 // ignore.
701                                         }
702                                 }
703                         }
704
705                         string xmlName = type.Name;
706                         if (type.IsGenericType) {
707                                 xmlName = xmlName.Substring (0, xmlName.IndexOf ('`')) + "Of";
708                                 foreach (var t in type.GetGenericArguments ())
709                                         xmlName += GetStaticQName (t).Name; // FIXME: check namespaces too
710                         }
711                         string xmlNamespace = GetDefaultNamespace (type);
712                         var x = GetAttribute<XmlRootAttribute> (type);
713                         if (x != null) {
714                                 xmlName = x.ElementName;
715                                 xmlNamespace = x.Namespace;
716                         }
717                         return new QName (XmlConvert.EncodeLocalName (xmlName), xmlNamespace);
718                 }
719
720                 static bool IsPrimitiveNotEnum (Type type)
721                 {
722                         if (type.IsEnum)
723                                 return false;
724                         if (Type.GetTypeCode (type) != TypeCode.Object) // explicitly primitive
725                                 return true;
726                         if (type == typeof (Guid) || type == typeof (object) || type == typeof(TimeSpan) || type == typeof(byte[]) || type == typeof(Uri) || type == typeof(DateTimeOffset)) // special primitives
727                                 return true;
728                         // DOM nodes
729                         if (type == typeof (XmlElement) || type == typeof (XmlNode []))
730                                 return true;
731                         // nullable
732                         if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>))
733                                 return IsPrimitiveNotEnum (type.GetGenericArguments () [0]);
734                         return false;
735                 }
736
737                 bool ShouldNotRegister (Type type)
738                 {
739                         return IsPrimitiveNotEnum (type);
740                 }
741
742                 internal bool TryRegister (Type type)
743                 {
744                         lock (this) {
745                                 return DoTryRegister (type);
746                         }
747                 }
748
749                 bool DoTryRegister (Type type)
750                 {
751                         // exclude predefined maps
752                         if (ShouldNotRegister (type))
753                                 return false;
754
755                         if (FindUserMap (type) != null)
756                                 return false;
757
758                         if (RegisterEnum (type) != null)
759                                 return true;
760
761                         if (RegisterDictionary (type) != null)
762                                 return true;
763
764                         if (RegisterCollectionContract (type) != null)
765                                 return true;
766
767                         if (RegisterContract (type) != null)
768                                 return true;
769
770                         if (RegisterIXmlSerializable (type) != null)
771                                 return true;
772
773                         if (RegisterCollection (type) != null)
774                                 return true;
775
776                         if (GetAttribute<SerializableAttribute> (type) != null) {
777                                 RegisterSerializable (type);
778                                 return true;
779                         }
780
781                         RegisterDefaultTypeMap (type);
782                         return true;
783                 }
784
785                 static Type GetCollectionElementType (Type type)
786                 {
787                         if (type.IsArray)
788                                 return type.GetElementType ();
789                         var ifaces = type.GetInterfacesOrSelfInterface ();
790                         foreach (Type i in ifaces)
791                                 if (i.IsGenericType && i.GetGenericTypeDefinition ().Equals (typeof (IEnumerable<>)))
792                                         return i.GetGenericArguments () [0];
793                         foreach (Type i in ifaces)
794                                 if (i == typeof (IEnumerable))
795                                         return typeof (object);
796                         return null;
797                 }
798
799                 internal static T GetAttribute<T> (ICustomAttributeProvider ap) where T : Attribute
800                 {
801                         object [] atts = ap.GetCustomAttributes (typeof (T), false);
802                         return atts.Length == 0 ? null : (T) atts [0];
803                 }
804
805                 private CollectionContractTypeMap RegisterCollectionContract (Type type)
806                 {
807                         var cdca = GetAttribute<CollectionDataContractAttribute> (type);
808                         if (cdca == null)
809                                 return null;
810
811                         Type element = GetCollectionElementType (type);
812                         if (element == null)
813                                 throw new InvalidDataContractException (String.Format ("Type '{0}' is marked as collection contract, but it is not a collection", type));
814                         if (type.GetMethod ("Add", new Type[] { element }) == null)
815                                 throw new InvalidDataContractException (String.Format ("Type '{0}' is marked as collection contract, but missing a public \"Add\" method", type));
816
817                         TryRegister (element); // must be registered before the name conflict check.
818
819                         QName qname = GetCollectionContractQName (type);
820                         CheckStandardQName (qname);
821                         var map = FindUserMap (qname, type);
822                         if (map != null) {
823                                 var cmap = map as CollectionContractTypeMap;
824                                 if (cmap == null) // The runtime type may still differ (between array and other IList; see bug #670560)
825                                         throw new InvalidOperationException (String.Format ("Failed to add type {0} to known type collection. There already is a registered type for XML name {1}", type, qname));
826                         }
827
828                         var ret = new CollectionContractTypeMap (type, cdca, element, qname, this);
829                         contracts.Add (ret);
830                         return ret;
831                 }
832
833                 private CollectionTypeMap RegisterCollection (Type type)
834                 {
835                         Type element = GetCollectionElementType (type);
836                         if (element == null)
837                                 return null;
838
839                         TryRegister (element);
840
841                         /*
842                          * To qualify as a custom collection type, a type must have
843                          * a public parameterless constructor and an "Add" method
844                          * with the correct parameter type in addition to implementing
845                          * one of the collection interfaces.
846                          * 
847                          */
848
849                         if (!type.IsArray && type.IsClass && !IsCustomCollectionType (type, element))
850                                 return null;
851
852                         QName qname = GetCollectionQName (element);
853
854                         var map = FindUserMap (qname, element);
855                         if (map != null) {
856                                 var cmap = map as CollectionTypeMap;
857                                 if (cmap == null) // The runtime type may still differ (between array and other IList; see bug #670560)
858                                         throw new InvalidOperationException (String.Format ("Failed to add type {0} to known type collection. There already is a registered type for XML name {1}", type, qname));
859                                 return cmap;
860                         }
861
862                         CollectionTypeMap ret =
863                                 new CollectionTypeMap (type, element, qname, this);
864                         contracts.Add (ret);
865                         return ret;
866                 }
867
868                 static bool IsCustomCollectionType (Type type, Type elementType)
869                 {
870                         if (!type.IsClass)
871                                 return false;
872                         if (type.GetConstructor (new Type [0]) == null)
873                                 return false;
874                         if (type.GetMethod ("Add", new Type[] { elementType }) == null)
875                                 return false;
876
877                         return true;
878                 }
879
880                 internal static bool IsInterchangeableCollectionType (Type contractType, Type graphType,
881                                                                       out QName collectionQName)
882                 {
883                         collectionQName = null;
884                         if (GetAttribute<CollectionDataContractAttribute> (contractType) != null)
885                                 return false;
886
887                         var type = contractType;
888                         if (type.IsGenericType)
889                                 type = type.GetGenericTypeDefinition ();
890
891                         var elementType = GetCollectionElementType (contractType);
892                         if (elementType == null)
893                                 return false;
894                         
895                         if (contractType.IsArray) {
896                                 if (!graphType.IsArray || !elementType.Equals (graphType.GetElementType ()))
897                                         throw new InvalidCastException (String.Format ("Type '{0}' cannot be converted into '{1}'.", graphType.GetElementType (), elementType));
898                         } else if (!contractType.IsInterface) {
899                                 if (GetAttribute<SerializableAttribute> (contractType) == null)
900                                         return false;
901
902                                 var graphElementType = GetCollectionElementType (graphType);
903                                 if (elementType != graphElementType)
904                                         return false;
905
906                                 if (!IsCustomCollectionType (contractType, elementType))
907                                         return false;
908                         } else if (type.Equals (typeof (IEnumerable)) || type.Equals (typeof (IList)) ||
909                                    type.Equals (typeof (ICollection))) {
910                                 if (!graphType.ImplementsInterface (contractType))
911                                         return false;
912                         } else if (type.Equals (typeof (IEnumerable<>)) || type.Equals (typeof (IList<>)) ||
913                                    type.Equals (typeof (ICollection<>))) {
914                                 var graphElementType = GetCollectionElementType (graphType);
915                                 if (graphElementType != elementType)
916                                         throw new InvalidCastException (String.Format (
917                                                 "Cannot convert type '{0}' into '{1}'.", graphType, contractType));
918
919                                 if (!graphType.ImplementsInterface (contractType))
920                                         return false;
921                         } else {
922                                 return false;
923                         }
924
925                         collectionQName = GetCollectionQName (elementType);
926                         return true;
927                 }
928
929                 static bool ImplementsInterface (Type type, Type iface)
930                 {
931                         foreach (var i in type.GetInterfacesOrSelfInterface ())
932                                 if (iface == i)
933                                         return true;
934                                         
935                         return false;
936                 }
937
938
939                 static bool TypeImplementsIEnumerable (Type type)
940                 {
941                         foreach (var iface in type.GetInterfacesOrSelfInterface ())
942                                 if (iface == typeof (IEnumerable) || (iface.IsGenericType && iface.GetGenericTypeDefinition () == typeof (IEnumerable<>)))
943                                         return true;
944                         
945                         return false;
946                 }
947
948                 static bool TypeImplementsIDictionary (Type type)
949                 {
950                         foreach (var iface in type.GetInterfacesOrSelfInterface ())
951                                 if (iface == typeof (IDictionary) || (iface.IsGenericType && iface.GetGenericTypeDefinition () == typeof (IDictionary<,>)))
952                                         return true;
953
954                         return false;
955                 }
956
957                 // it also supports contract-based dictionary.
958                 private DictionaryTypeMap RegisterDictionary (Type type)
959                 {
960                         if (!TypeImplementsIDictionary (type))
961                                 return null;
962
963                         var cdca = GetAttribute<CollectionDataContractAttribute> (type);
964
965                         DictionaryTypeMap ret =
966                                 new DictionaryTypeMap (type, cdca, this);
967
968                         TryRegister (ret.KeyType);
969                         TryRegister (ret.ValueType);
970
971                         var map = FindUserMap (ret.XmlName, type);
972                         if (map != null) {
973                                 var dmap = map as DictionaryTypeMap;
974                                 if (dmap == null) // The runtime type may still differ (between array and other IList; see bug #670560)
975                                         throw new InvalidOperationException (String.Format ("Failed to add type {0} to known type collection. There already is a registered type for XML name {1}", type, ret.XmlName));
976                         }
977                         contracts.Add (ret);
978
979                         return ret;
980                 }
981
982                 private SerializationMap RegisterSerializable (Type type)
983                 {
984                         QName qname = GetSerializableQName (type);
985
986                         if (FindUserMap (qname, type) != null)
987                                 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
988
989                         SharedTypeMap ret = new SharedTypeMap (type, qname, this);
990                         contracts.Add (ret);
991                         ret.Initialize ();
992                         return ret;
993                 }
994
995                 private SerializationMap RegisterIXmlSerializable (Type type)
996                 {
997                         if (type.GetInterface ("System.Xml.Serialization.IXmlSerializable") == null)
998                                 return null;
999
1000                         QName qname = GetSerializableQName (type);
1001
1002                         if (!QName.Empty.Equals (qname) && FindUserMap (qname, type) != null)
1003                                 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
1004
1005                         XmlSerializableMap ret = new XmlSerializableMap (type, qname, this);
1006                         contracts.Add (ret);
1007
1008                         return ret;
1009                 }
1010
1011                 void CheckStandardQName (QName qname)
1012                 {
1013                         switch (qname.Namespace) {
1014                         case XmlSchema.Namespace:
1015                         case XmlSchema.InstanceNamespace:
1016                         case MSSimpleNamespace:
1017                         case MSArraysNamespace:
1018                                 throw new InvalidOperationException (String.Format ("Namespace {0} is reserved and cannot be used for user serialization", qname.Namespace));
1019                         }
1020
1021                 }
1022
1023                 private SharedContractMap RegisterContract (Type type)
1024                 {
1025                         QName qname = GetContractQName (type);
1026                         if (qname == null)
1027                                 return null;
1028                         CheckStandardQName (qname);
1029                         if (FindUserMap (qname, type) != null)
1030                                 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
1031
1032                         SharedContractMap ret = new SharedContractMap (type, qname, this);
1033                         contracts.Add (ret);
1034                         ret.Initialize ();
1035
1036                         if (type.BaseType != typeof (object)) {
1037                                 TryRegister (type.BaseType);
1038                                 if (!FindUserMap (type.BaseType).IsContractAllowedType)
1039                                         throw new InvalidDataContractException (String.Format ("To be serializable by data contract, type '{0}' cannot inherit from non-contract and non-Serializable type '{1}'", type, type.BaseType));
1040                         }
1041
1042                         object [] attrs = type.GetCustomAttributes (typeof (KnownTypeAttribute), true);
1043                         for (int i = 0; i < attrs.Length; i++) {
1044                                 KnownTypeAttribute kt = (KnownTypeAttribute) attrs [i];
1045                                 foreach (var t in kt.GetTypes (type))
1046                                         TryRegister (t);
1047                         }
1048
1049                         return ret;
1050                 }
1051
1052                 DefaultTypeMap RegisterDefaultTypeMap (Type type)
1053                 {
1054                         DefaultTypeMap ret = new DefaultTypeMap (type, this);
1055                         contracts.Add (ret);
1056                         ret.Initialize ();
1057                         return ret;
1058                 }
1059
1060                 private EnumMap RegisterEnum (Type type)
1061                 {
1062                         QName qname = GetEnumQName (type);
1063                         if (qname == null)
1064                                 return null;
1065
1066                         if (FindUserMap (qname, type) != null)
1067                                 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
1068
1069                         EnumMap ret =
1070                                 new EnumMap (type, qname, this);
1071                         contracts.Add (ret);
1072                         return ret;
1073                 }
1074         }
1075 }
1076 #endif