Some attempts to make KnownTypeCollection parallel access safe.
[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         internal static class TypeExtensions
78         {
79                 public static T GetCustomAttribute<T> (this MemberInfo type, bool inherit)
80                 {
81                         var arr = type.GetCustomAttributes (typeof (T), inherit);
82                         return arr != null && arr.Length == 1 ? (T) arr [0] : default (T);
83                 }
84
85                 public static IEnumerable<Type> GetInterfacesOrSelfInterface (this Type type)
86                 {
87                         if (type.IsInterface)
88                                 yield return type;
89                         foreach (var t in type.GetInterfaces ())
90                                 yield return t;
91                 }
92         }
93
94         internal sealed class KnownTypeCollection : Collection<Type>
95         {
96                 internal const string MSSimpleNamespace =
97                         "http://schemas.microsoft.com/2003/10/Serialization/";
98                 internal const string MSArraysNamespace =
99                         "http://schemas.microsoft.com/2003/10/Serialization/Arrays";
100                 internal const string DefaultClrNamespaceBase =
101                         "http://schemas.datacontract.org/2004/07/";
102                 internal const string DefaultClrNamespaceSystem =
103                         "http://schemas.datacontract.org/2004/07/System";
104
105
106                 static QName any_type, bool_type,
107                         byte_type, date_type, decimal_type, double_type,
108                         float_type, string_type,
109                         short_type, int_type, long_type,
110                         ubyte_type, ushort_type, uint_type, ulong_type,
111                         // non-TypeCode
112                         any_uri_type, base64_type, duration_type, qname_type,
113                         // custom in ms nsURI schema
114                         char_type, guid_type,
115                         // not in ms nsURI schema
116                         dbnull_type, date_time_offset_type;
117
118                 static KnownTypeCollection ()
119                 {
120                         string s = MSSimpleNamespace;
121                         any_type = new QName ("anyType", s);
122                         any_uri_type = new QName ("anyURI", s);
123                         bool_type = new QName ("boolean", s);
124                         base64_type = new QName ("base64Binary", s);
125                         date_type = new QName ("dateTime", s);
126                         duration_type = new QName ("duration", s);
127                         qname_type = new QName ("QName", s);
128                         decimal_type = new QName ("decimal", s);
129                         double_type = new QName ("double", s);
130                         float_type = new QName ("float", s);
131                         byte_type = new QName ("byte", s);
132                         short_type = new QName ("short", s);
133                         int_type = new QName ("int", s);
134                         long_type = new QName ("long", s);
135                         ubyte_type = new QName ("unsignedByte", s);
136                         ushort_type = new QName ("unsignedShort", s);
137                         uint_type = new QName ("unsignedInt", s);
138                         ulong_type = new QName ("unsignedLong", s);
139                         string_type = new QName ("string", s);
140                         guid_type = new QName ("guid", s);
141                         char_type = new QName ("char", s);
142
143                         dbnull_type = new QName ("DBNull", DefaultClrNamespaceBase + "System");
144                         date_time_offset_type = new QName ("DateTimeOffset", DefaultClrNamespaceBase + "System");
145                 }
146
147                 // FIXME: find out how QName and guid are processed
148
149                 internal QName GetXmlName (Type type)
150                 {
151                         SerializationMap map = FindUserMap (type);
152                         if (map != null)
153                                 return map.XmlName;
154                         return GetPredefinedTypeName (type);
155                 }
156
157                 internal static QName GetPredefinedTypeName (Type type)
158                 {
159                         QName name = GetPrimitiveTypeName (type);
160                         if (name != QName.Empty)
161                                 return name;
162                         if (type == typeof (DBNull))
163                                 return dbnull_type;
164                         if (type == typeof (DateTimeOffset))
165                                 return date_time_offset_type;
166                         return QName.Empty;
167                 }
168
169                 internal static QName GetPrimitiveTypeName (Type type)
170                 {
171                         if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>))
172                                 return GetPrimitiveTypeName (type.GetGenericArguments () [0]);
173
174                         if (type.IsEnum)
175                                 return QName.Empty;
176
177                         switch (Type.GetTypeCode (type)) {
178                         case TypeCode.Object: // other than System.Object
179                         case TypeCode.DBNull: // it is natively mapped, but not in ms serialization namespace.
180                         case TypeCode.Empty:
181                         default:
182                                 if (type == typeof (object))
183                                         return any_type;
184                                 if (type == typeof (Guid))
185                                         return guid_type;
186                                 if (type == typeof (TimeSpan))
187                                         return duration_type;
188                                 if (type == typeof (byte []))
189                                         return base64_type;
190                                 if (type == typeof (Uri))
191                                         return any_uri_type;
192                                 return QName.Empty;
193                         case TypeCode.Boolean:
194                                 return bool_type;
195                         case TypeCode.Byte:
196                                 return ubyte_type;
197                         case TypeCode.Char:
198                                 return char_type;
199                         case TypeCode.DateTime:
200                                 return date_type;
201                         case TypeCode.Decimal:
202                                 return decimal_type;
203                         case TypeCode.Double:
204                                 return double_type;
205                         case TypeCode.Int16:
206                                 return short_type;
207                         case TypeCode.Int32:
208                                 return int_type;
209                         case TypeCode.Int64:
210                                 return long_type;
211                         case TypeCode.SByte:
212                                 return byte_type;
213                         case TypeCode.Single:
214                                 return float_type;
215                         case TypeCode.String:
216                                 return string_type;
217                         case TypeCode.UInt16:
218                                 return ushort_type;
219                         case TypeCode.UInt32:
220                                 return uint_type;
221                         case TypeCode.UInt64:
222                                 return ulong_type;
223                         }
224                 }
225
226                 internal static string PredefinedTypeObjectToString (object obj)
227                 {
228                         Type type = obj.GetType ();
229                         switch (Type.GetTypeCode (type)) {
230                         case TypeCode.Object: // other than System.Object
231                         case TypeCode.Empty:
232                         default:
233                                 if (type == typeof (object))
234                                         return String.Empty;
235                                 if (type == typeof (Guid))
236                                         return XmlConvert.ToString ((Guid) obj);
237                                 if (type == typeof (TimeSpan))
238                                         return XmlConvert.ToString ((TimeSpan) obj);
239                                 if (type == typeof (byte []))
240                                         return Convert.ToBase64String ((byte []) obj);
241                                 if (type == typeof (Uri))
242                                         return ((Uri) obj).ToString ();
243                                 throw new Exception ("Internal error: missing predefined type serialization for type " + type.FullName);
244                         case TypeCode.DBNull: // predefined, but not primitive
245                                 return String.Empty;
246                         case TypeCode.Boolean:
247                                 return XmlConvert.ToString ((bool) obj);
248                         case TypeCode.Byte:
249                                 return XmlConvert.ToString ((int)((byte) obj));
250                         case TypeCode.Char:
251                                 return XmlConvert.ToString ((uint) (char) obj);
252                         case TypeCode.DateTime:
253                                 return XmlConvert.ToString ((DateTime) obj, XmlDateTimeSerializationMode.RoundtripKind);
254                         case TypeCode.Decimal:
255                                 return XmlConvert.ToString ((decimal) obj);
256                         case TypeCode.Double:
257                                 return XmlConvert.ToString ((double) obj);
258                         case TypeCode.Int16:
259                                 return XmlConvert.ToString ((short) obj);
260                         case TypeCode.Int32:
261                                 return XmlConvert.ToString ((int) obj);
262                         case TypeCode.Int64:
263                                 return XmlConvert.ToString ((long) obj);
264                         case TypeCode.SByte:
265                                 return XmlConvert.ToString ((sbyte) obj);
266                         case TypeCode.Single:
267                                 return XmlConvert.ToString ((float) obj);
268                         case TypeCode.String:
269                                 return (string) obj;
270                         case TypeCode.UInt16:
271                                 return XmlConvert.ToString ((int) (ushort) obj);
272                         case TypeCode.UInt32:
273                                 return XmlConvert.ToString ((uint) obj);
274                         case TypeCode.UInt64:
275                                 return XmlConvert.ToString ((ulong) obj);
276                         }
277                 }
278
279                 internal static Type GetPrimitiveTypeFromName (QName name)
280                 {
281                         switch (name.Namespace) {
282                         case DefaultClrNamespaceSystem:
283                                 switch (name.Name) {
284                                 case "DBNull":
285                                         return typeof (DBNull);
286                                 case "DateTimeOffset":
287                                         return typeof (DateTimeOffset);
288                                 }
289                                 break;
290                         // FIXME: xsd types and ms serialization types should be differentiated. (usage problem)
291                         case XmlSchema.Namespace:
292                         case MSSimpleNamespace:
293                                 switch (name.Name) {
294                                 case "anyURI":
295                                         return typeof (Uri);
296                                 case "boolean":
297                                         return typeof (bool);
298                                 case "base64Binary":
299                                         return typeof (byte []);
300                                 case "dateTime":
301                                         return typeof (DateTime);
302                                 case "duration":
303                                         return typeof (TimeSpan);
304                                 case "QName":
305                                         return typeof (QName);
306                                 case "decimal":
307                                         return typeof (decimal);
308                                 case "double":
309                                         return typeof (double);
310                                 case "float":
311                                         return typeof (float);
312                                 case "byte":
313                                         return typeof (sbyte);
314                                 case "short":
315                                         return typeof (short);
316                                 case "int":
317                                         return typeof (int);
318                                 case "long":
319                                         return typeof (long);
320                                 case "unsignedByte":
321                                         return typeof (byte);
322                                 case "unsignedShort":
323                                         return typeof (ushort);
324                                 case "unsignedInt":
325                                         return typeof (uint);
326                                 case "unsignedLong":
327                                         return typeof (ulong);
328                                 case "string":
329                                         return typeof (string);
330                                 case "anyType":
331                                         return typeof (object);
332                                 case "guid":
333                                         return typeof (Guid);
334                                 case "char":
335                                         return typeof (char);
336                                 }
337                                 break;
338                         }
339                         return null;
340                 }
341
342
343                 internal static object PredefinedTypeStringToObject (string s,
344                         string name, XmlReader reader)
345                 {
346                         switch (name) {
347                         case "anyURI":
348                                 return new Uri(s,UriKind.RelativeOrAbsolute);
349                         case "boolean":
350                                 return XmlConvert.ToBoolean (s);
351                         case "base64Binary":
352                                 return Convert.FromBase64String (s);
353                         case "dateTime":
354                                 return XmlConvert.ToDateTime (s, XmlDateTimeSerializationMode.RoundtripKind);
355                         case "duration":
356                                 return XmlConvert.ToTimeSpan (s);
357                         case "QName":
358                                 int idx = s.IndexOf (':');
359                                 string l = idx < 0 ? s : s.Substring (idx + 1);
360                                 return idx < 0 ? new QName (l) :
361                                         new QName (l, reader.LookupNamespace (
362                                                 s.Substring (0, idx)));
363                         case "decimal":
364                                 return XmlConvert.ToDecimal (s);
365                         case "double":
366                                 return XmlConvert.ToDouble (s);
367                         case "float":
368                                 return XmlConvert.ToSingle (s);
369                         case "byte":
370                                 return XmlConvert.ToSByte (s);
371                         case "short":
372                                 return XmlConvert.ToInt16 (s);
373                         case "int":
374                                 return XmlConvert.ToInt32 (s);
375                         case "long":
376                                 return XmlConvert.ToInt64 (s);
377                         case "unsignedByte":
378                                 return XmlConvert.ToByte (s);
379                         case "unsignedShort":
380                                 return XmlConvert.ToUInt16 (s);
381                         case "unsignedInt":
382                                 return XmlConvert.ToUInt32 (s);
383                         case "unsignedLong":
384                                 return XmlConvert.ToUInt64 (s);
385                         case "string":
386                                 return s;
387                         case "guid":
388                                 return XmlConvert.ToGuid (s);
389                         case "anyType":
390                                 return s;
391                         case "char":
392                                 return (char) XmlConvert.ToUInt32 (s);
393                         default:
394                                 throw new Exception ("Unanticipated primitive type: " + name);
395                         }
396                 }
397
398                 List<SerializationMap> contracts = new List<SerializationMap> ();
399
400                 public KnownTypeCollection ()
401                 {
402                 }
403
404                 protected override void ClearItems ()
405                 {
406                         base.Clear ();
407                 }
408
409                 protected override void InsertItem (int index, Type type)
410                 {
411                         if (ShouldNotRegister (type))
412                                 return;
413                         if (!Contains (type)) {
414                                 TryRegister (type);
415                                 base.InsertItem (index, type);
416                         }
417                 }
418
419                 // FIXME: it could remove other types' dependencies.
420                 protected override void RemoveItem (int index)
421                 {
422                         lock (this)
423                                 DoRemoveItem (index);
424                 }
425
426                 void DoRemoveItem (int index)
427                 {
428                         Type t = base [index];
429                         List<SerializationMap> l = new List<SerializationMap> ();
430                         foreach (SerializationMap m in contracts) {
431                                 if (m.RuntimeType == t)
432                                         l.Add (m);
433                         }
434                         foreach (SerializationMap m in l) {
435                                 contracts.Remove (m);
436                                 base.RemoveItem (index);
437                         }
438                 }
439
440                 protected override void SetItem (int index, Type type)
441                 {
442                         if (ShouldNotRegister (type))
443                                 return;
444
445                         // Since this collection is not assured to be ordered, it ignores the whole Set operation if the type already exists.
446                         if (Contains (type))
447                                 return;
448
449                         if (index != Count)
450                                 RemoveItem (index);
451                         if (TryRegister (type))
452                                 base.InsertItem (index - 1, type);
453                 }
454
455                 internal SerializationMap FindUserMap (QName qname)
456                 {
457                         lock (this)
458                                 return contracts.FirstOrDefault (c => c.XmlName == qname);
459                 }
460
461                 internal Type GetSerializedType (Type type)
462                 {
463                         if (IsPrimitiveNotEnum (type))
464                                 return type;
465                         Type element = GetCollectionElementType (type);
466                         if (element == null)
467                                 return type;
468                         QName name = GetQName (type);
469                         var map = FindUserMap (name);
470                         if (map != null)
471                                 return map.RuntimeType;
472                         return type;
473                 }
474
475                 internal SerializationMap FindUserMap (Type type)
476                 {
477                         lock (this) {
478                                 for (int i = 0; i < contracts.Count; i++)
479                                         if (type == contracts [i].RuntimeType)
480                                                 return contracts [i];
481                                 return null;
482                         }
483                 }
484
485                 internal QName GetQName (Type type)
486                 {
487                         SerializationMap map = FindUserMap (type);
488                         if (map != null)
489                                 // already mapped.
490                                 return map.XmlName; 
491                         return GetStaticQName (type);
492                 }
493
494                 public static QName GetStaticQName (Type type)
495                 {
496                         if (IsPrimitiveNotEnum (type))
497                                 return GetPrimitiveTypeName (type);
498
499                         if (type.IsEnum)
500                                 return GetEnumQName (type);
501
502                         QName qname = GetContractQName (type);
503                         if (qname != null)
504                                 return qname;
505
506                         if (type.GetInterface ("System.Xml.Serialization.IXmlSerializable") != null)
507                                 //FIXME: Reusing GetSerializableQName here, since we just 
508                                 //need name of the type..
509                                 return GetSerializableQName (type);
510
511                         qname = GetCollectionContractQName (type);
512                         if (qname != null)
513                                 return qname;
514
515                         Type element = GetCollectionElementType (type);
516                         if (element != null)
517                                 return GetCollectionQName (element);
518
519                         if (GetAttribute<SerializableAttribute> (type) != null)
520                                 return GetSerializableQName (type);
521
522                         // default type map - still uses GetContractQName().
523                         return GetContractQName (type, null, null);
524                 }
525
526                 internal static QName GetContractQName (Type type)
527                 {
528                         var a = GetAttribute<DataContractAttribute> (type);
529                         return a == null ? null : GetContractQName (type, a.Name, a.Namespace);
530                 }
531
532                 static QName GetCollectionContractQName (Type type)
533                 {
534                         var a = GetAttribute<CollectionDataContractAttribute> (type);
535                         return a == null ? null : GetContractQName (type, a.Name, a.Namespace);
536                 }
537
538                 static QName GetContractQName (Type type, string name, string ns)
539                 {
540                         if (name == null)
541                                 name = GetDefaultName (type);
542                         if (ns == null)
543                                 ns = GetDefaultNamespace (type);
544                         return new QName (name, ns);
545                 }
546
547                 static QName GetEnumQName (Type type)
548                 {
549                         string name = null, ns = null;
550
551                         if (!type.IsEnum)
552                                 return null;
553
554                         var dca = GetAttribute<DataContractAttribute> (type);
555
556                         if (dca != null) {
557                                 ns = dca.Namespace;
558                                 name = dca.Name;
559                         }
560
561                         if (ns == null)
562                                 ns = GetDefaultNamespace (type);
563
564                         if (name == null)
565                                 name = type.Namespace == null ? type.Name : type.FullName.Substring (type.Namespace.Length + 1).Replace ('+', '.');
566
567                         return new QName (name, ns);
568                 }
569
570                 internal static string GetDefaultName (Type type)
571                 {
572                         // FIXME: there could be decent ways to get 
573                         // the same result...
574                         string name = type.Namespace == null || type.Namespace.Length == 0 ? type.Name : type.FullName.Substring (type.Namespace.Length + 1).Replace ('+', '.');
575                         if (type.IsGenericType) {
576                                 name = name.Substring (0, name.IndexOf ('`')) + "Of";
577                                 foreach (var t in type.GetGenericArguments ())
578                                         name += t.Name; // FIXME: check namespaces too
579                         }
580                         return name;
581                 }
582
583                 internal static string GetDefaultNamespace (Type type)
584                 {
585                         foreach (ContractNamespaceAttribute a in type.Assembly.GetCustomAttributes (typeof (ContractNamespaceAttribute), true))
586                                 if (a.ClrNamespace == type.Namespace)
587                                         return a.ContractNamespace;
588                         return DefaultClrNamespaceBase + type.Namespace;
589                 }
590
591                 static QName GetCollectionQName (Type element)
592                 {
593                         QName eqname = GetStaticQName (element);
594                         
595                         string ns = eqname.Namespace;
596                         if (eqname.Namespace == MSSimpleNamespace)
597                                 //Arrays of Primitive types
598                                 ns = MSArraysNamespace;
599
600                         return new QName (
601                                 "ArrayOf" + XmlConvert.EncodeLocalName (eqname.Name),
602                                 ns);
603                 }
604
605                 static QName GetSerializableQName (Type type)
606                 {
607 #if !NET_2_1
608                         // First, check XmlSchemaProviderAttribute and try GetSchema() to see if it returns a schema in the expected format.
609                         var xpa = type.GetCustomAttribute<XmlSchemaProviderAttribute> (true);
610                         if (xpa != null) {
611                                 var mi = type.GetMethod (xpa.MethodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
612                                 if (mi != null) {
613                                         try {
614                                                 var xss = new XmlSchemaSet ();
615                                                 return (XmlQualifiedName) mi.Invoke (null, new object [] {xss});
616                                         } catch {
617                                                 // ignore.
618                                         }
619                                 }
620                         }
621 #endif
622
623                         string xmlName = type.Name;
624                         if (type.IsGenericType) {
625                                 xmlName = xmlName.Substring (0, xmlName.IndexOf ('`')) + "Of";
626                                 foreach (var t in type.GetGenericArguments ())
627                                         xmlName += GetStaticQName (t).Name; // FIXME: check namespaces too
628                         }
629                         string xmlNamespace = GetDefaultNamespace (type);
630                         var x = GetAttribute<XmlRootAttribute> (type);
631                         if (x != null) {
632                                 xmlName = x.ElementName;
633                                 xmlNamespace = x.Namespace;
634                         }
635                         return new QName (XmlConvert.EncodeLocalName (xmlName), xmlNamespace);
636                 }
637
638                 static bool IsPrimitiveNotEnum (Type type)
639                 {
640                         if (type.IsEnum)
641                                 return false;
642                         if (Type.GetTypeCode (type) != TypeCode.Object) // explicitly primitive
643                                 return true;
644                         if (type == typeof (Guid) || type == typeof (object) || type == typeof(TimeSpan) || type == typeof(byte[]) || type==typeof(Uri)) // special primitives
645                                 return true;
646                         // nullable
647                         if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>))
648                                 return IsPrimitiveNotEnum (type.GetGenericArguments () [0]);
649                         return false;
650                 }
651
652                 bool ShouldNotRegister (Type type)
653                 {
654                         return IsPrimitiveNotEnum (type);
655                 }
656
657                 internal bool TryRegister (Type type)
658                 {
659                         lock (this) {
660                                 return DoTryRegister (type);
661                         }
662                 }
663
664                 bool DoTryRegister (Type type)
665                 {
666                         // exclude predefined maps
667                         if (ShouldNotRegister (type))
668                                 return false;
669
670                         if (FindUserMap (type) != null)
671                                 return false;
672
673                         if (RegisterEnum (type) != null)
674                                 return true;
675
676                         if (RegisterDictionary (type) != null)
677                                 return true;
678
679                         if (RegisterCollectionContract (type) != null)
680                                 return true;
681
682                         if (RegisterContract (type) != null)
683                                 return true;
684
685                         if (RegisterIXmlSerializable (type) != null)
686                                 return true;
687
688                         if (RegisterCollection (type) != null)
689                                 return true;
690
691                         if (GetAttribute<SerializableAttribute> (type) != null) {
692                                 RegisterSerializable (type);
693                                 return true;
694                         }
695
696                         RegisterDefaultTypeMap (type);
697                         return true;
698                 }
699
700                 static Type GetCollectionElementType (Type type)
701                 {
702                         if (type.IsArray)
703                                 return type.GetElementType ();
704                         var ifaces = type.GetInterfacesOrSelfInterface ();
705                         foreach (Type i in ifaces)
706                                 if (i.IsGenericType && i.GetGenericTypeDefinition ().Equals (typeof (IEnumerable<>)))
707                                         return i.GetGenericArguments () [0];
708                         foreach (Type i in ifaces)
709                                 if (i == typeof (IList))
710                                         return typeof (object);
711                         return null;
712                 }
713
714                 internal static T GetAttribute<T> (ICustomAttributeProvider ap) where T : Attribute
715                 {
716                         object [] atts = ap.GetCustomAttributes (typeof (T), false);
717                         return atts.Length == 0 ? null : (T) atts [0];
718                 }
719
720                 private CollectionContractTypeMap RegisterCollectionContract (Type type)
721                 {
722                         var cdca = GetAttribute<CollectionDataContractAttribute> (type);
723                         if (cdca == null)
724                                 return null;
725
726                         Type element = GetCollectionElementType (type);
727                         if (element == null)
728                                 throw new InvalidOperationException (String.Format ("Type '{0}' is marked as collection contract, but it is not a collection", type));
729
730                         TryRegister (element); // must be registered before the name conflict check.
731
732                         QName qname = GetCollectionContractQName (type);
733                         CheckStandardQName (qname);
734                         var map = FindUserMap (qname);
735                         if (map != null) {
736                                 var cmap = map as CollectionContractTypeMap;
737                                 if (cmap == null) // The runtime type may still differ (between array and other IList; see bug #670560)
738                                         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));
739                         }
740
741                         var ret = new CollectionContractTypeMap (type, cdca, element, qname, this);
742                         contracts.Add (ret);
743                         return ret;
744                 }
745
746                 private CollectionTypeMap RegisterCollection (Type type)
747                 {
748                         Type element = GetCollectionElementType (type);
749                         if (element == null)
750                                 return null;
751
752                         TryRegister (element);
753
754                         QName qname = GetCollectionQName (element);
755
756                         var map = FindUserMap (qname);
757                         if (map != null) {
758                                 var cmap = map as CollectionTypeMap;
759                                 if (cmap == null) // The runtime type may still differ (between array and other IList; see bug #670560)
760                                         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));
761                                 return cmap;
762                         }
763
764                         CollectionTypeMap ret =
765                                 new CollectionTypeMap (type, element, qname, this);
766                         contracts.Add (ret);
767                         return ret;
768                 }
769
770                 static bool TypeImplementsIDictionary (Type type)
771                 {
772                         foreach (var iface in type.GetInterfacesOrSelfInterface ())
773                                 if (iface == typeof (IDictionary) || (iface.IsGenericType && iface.GetGenericTypeDefinition () == typeof (IDictionary<,>)))
774                                         return true;
775
776                         return false;
777                 }
778
779                 // it also supports contract-based dictionary.
780                 private DictionaryTypeMap RegisterDictionary (Type type)
781                 {
782                         if (!TypeImplementsIDictionary (type))
783                                 return null;
784
785                         var cdca = GetAttribute<CollectionDataContractAttribute> (type);
786
787                         DictionaryTypeMap ret =
788                                 new DictionaryTypeMap (type, cdca, this);
789
790                         TryRegister (ret.KeyType);
791                         TryRegister (ret.ValueType);
792
793                         var map = FindUserMap (ret.XmlName);
794                         if (map != null) {
795                                 var dmap = map as DictionaryTypeMap;
796                                 if (dmap == null) // The runtime type may still differ (between array and other IList; see bug #670560)
797                                         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));
798                         }
799                         contracts.Add (ret);
800
801                         return ret;
802                 }
803
804                 private SerializationMap RegisterSerializable (Type type)
805                 {
806                         QName qname = GetSerializableQName (type);
807
808                         if (FindUserMap (qname) != null)
809                                 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
810
811                         SharedTypeMap ret = new SharedTypeMap (type, qname, this);
812                         contracts.Add (ret);
813                         ret.Initialize ();
814                         return ret;
815                 }
816
817                 private SerializationMap RegisterIXmlSerializable (Type type)
818                 {
819                         if (type.GetInterface ("System.Xml.Serialization.IXmlSerializable") == null)
820                                 return null;
821
822                         QName qname = GetSerializableQName (type);
823
824                         if (FindUserMap (qname) != null)
825                                 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
826
827                         XmlSerializableMap ret = new XmlSerializableMap (type, qname, this);
828                         contracts.Add (ret);
829
830                         return ret;
831                 }
832
833                 void CheckStandardQName (QName qname)
834                 {
835                         switch (qname.Namespace) {
836                         case XmlSchema.Namespace:
837                         case XmlSchema.InstanceNamespace:
838                         case MSSimpleNamespace:
839                         case MSArraysNamespace:
840                                 throw new InvalidOperationException (String.Format ("Namespace {0} is reserved and cannot be used for user serialization", qname.Namespace));
841                         }
842
843                 }
844
845                 private SharedContractMap RegisterContract (Type type)
846                 {
847                         QName qname = GetContractQName (type);
848                         if (qname == null)
849                                 return null;
850                         CheckStandardQName (qname);
851                         if (FindUserMap (qname) != null)
852                                 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
853
854                         SharedContractMap ret = new SharedContractMap (type, qname, this);
855                         contracts.Add (ret);
856                         ret.Initialize ();
857
858                         if (type.BaseType != typeof (object)) {
859                                 TryRegister (type.BaseType);
860                                 if (!FindUserMap (type.BaseType).IsContractAllowedType)
861                                         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));
862                         }
863
864                         object [] attrs = type.GetCustomAttributes (typeof (KnownTypeAttribute), true);
865                         for (int i = 0; i < attrs.Length; i++) {
866                                 KnownTypeAttribute kt = (KnownTypeAttribute) attrs [i];
867                                 TryRegister (kt.Type);
868                         }
869
870                         return ret;
871                 }
872
873                 DefaultTypeMap RegisterDefaultTypeMap (Type type)
874                 {
875                         DefaultTypeMap ret = new DefaultTypeMap (type, this);
876                         contracts.Add (ret);
877                         ret.Initialize ();
878                         return ret;
879                 }
880
881                 private EnumMap RegisterEnum (Type type)
882                 {
883                         QName qname = GetEnumQName (type);
884                         if (qname == null)
885                                 return null;
886
887                         if (FindUserMap (qname) != null)
888                                 throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
889
890                         EnumMap ret =
891                                 new EnumMap (type, qname, this);
892                         contracts.Add (ret);
893                         return ret;
894                 }
895         }
896 }
897 #endif