// // KnownTypeCollection.cs // // Author: // Atsushi Enomoto // // Copyright (C) 2005 Novell, Inc. http://www.novell.com // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #if NET_2_0 using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reflection; using System.Xml; using System.Xml.Schema; using QName = System.Xml.XmlQualifiedName; using System.Xml.Serialization; namespace System.Runtime.Serialization { /* XmlFormatter implementation design inference: type definitions: - No XML Schema types are directly used. There are some maps from xs:blahType to ms:blahType where the namespaceURI for prefix "ms" is "http://schemas.microsoft.com/2003/10/Serialization/" . serializable types: - An object being serialized 1) must be of type System.Object, or 2) must be null, or 3) must have either a [DataContract] attribute or a [Serializable] attribute to be serializable. - When the object is either of type System.Object or null, then the XML type is "anyType". - When the object is [Serializable], then the runtime-serialization compatible object graph is written. - Otherwise the serialization is based on contract attributes. ([Serializable] takes precedence). type derivation: - For type A to be serializable, the base type B of A must be serializable. - If a type which is [Serializable] and whose base type has a [DataContract], then for base type members [DataContract] is taken. - It is vice versa i.e. if the base type is [Serializable] and the derived type has a [DataContract], then [Serializable] takes place for base members. known type collection: - It internally manages mapping store keyed by contract QNames. KnownTypeCollection.Add() checks if the same QName contract already exists (and raises InvalidOperationException if required). */ internal static class TypeExtensions { public static T GetCustomAttribute (this Type type, bool inherit) { var arr = type.GetCustomAttributes (typeof (T), inherit); return arr != null && arr.Length == 1 ? (T) arr [0] : default (T); } } internal sealed class KnownTypeCollection : Collection { internal const string MSSimpleNamespace = "http://schemas.microsoft.com/2003/10/Serialization/"; internal const string MSArraysNamespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays"; internal const string DefaultClrNamespaceBase = "http://schemas.datacontract.org/2004/07/"; static QName any_type, bool_type, byte_type, date_type, decimal_type, double_type, float_type, string_type, short_type, int_type, long_type, ubyte_type, ushort_type, uint_type, ulong_type, // non-TypeCode any_uri_type, base64_type, duration_type, qname_type, // custom in ms nsURI schema char_type, guid_type, // not in ms nsURI schema dbnull_type; static KnownTypeCollection () { //any_type, bool_type, byte_type, date_type, decimal_type, double_type, float_type, string_type, // short_type, int_type, long_type, ubyte_type, ushort_type, uint_type, ulong_type, // any_uri_type, base64_type, duration_type, qname_type, // char_type, guid_type, dbnull_type; string s = MSSimpleNamespace; any_type = new QName ("anyType", s); any_uri_type = new QName ("anyURI", s); bool_type = new QName ("boolean", s); base64_type = new QName ("base64Binary", s); date_type = new QName ("dateTime", s); duration_type = new QName ("duration", s); qname_type = new QName ("QName", s); decimal_type = new QName ("decimal", s); double_type = new QName ("double", s); float_type = new QName ("float", s); byte_type = new QName ("byte", s); short_type = new QName ("short", s); int_type = new QName ("int", s); long_type = new QName ("long", s); ubyte_type = new QName ("unsignedByte", s); ushort_type = new QName ("unsignedShort", s); uint_type = new QName ("unsignedInt", s); ulong_type = new QName ("unsignedLong", s); string_type = new QName ("string", s); guid_type = new QName ("guid", s); char_type = new QName ("char", s); dbnull_type = new QName ("DBNull", MSSimpleNamespace + "System"); } // FIXME: find out how QName and guid are processed internal QName GetXmlName (Type type) { SerializationMap map = FindUserMap (type); if (map != null) return map.XmlName; return GetPredefinedTypeName (type); } internal static QName GetPredefinedTypeName (Type type) { QName name = GetPrimitiveTypeName (type); if (name != QName.Empty) return name; if (type == typeof (DBNull)) return dbnull_type; return QName.Empty; } internal static QName GetPrimitiveTypeName (Type type) { if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>)) return GetPrimitiveTypeName (type.GetGenericArguments () [0]); if (type.IsEnum) return QName.Empty; switch (Type.GetTypeCode (type)) { case TypeCode.Object: // other than System.Object case TypeCode.DBNull: // it is natively mapped, but not in ms serialization namespace. case TypeCode.Empty: default: if (type == typeof (object)) return any_type; if (type == typeof (Guid)) return guid_type; if (type == typeof (TimeSpan)) return duration_type; if (type == typeof (byte [])) return base64_type; if (type == typeof (Uri)) return any_uri_type; return QName.Empty; case TypeCode.Boolean: return bool_type; case TypeCode.Byte: return ubyte_type; case TypeCode.Char: return char_type; case TypeCode.DateTime: return date_type; case TypeCode.Decimal: return decimal_type; case TypeCode.Double: return double_type; case TypeCode.Int16: return short_type; case TypeCode.Int32: return int_type; case TypeCode.Int64: return long_type; case TypeCode.SByte: return byte_type; case TypeCode.Single: return float_type; case TypeCode.String: return string_type; case TypeCode.UInt16: return ushort_type; case TypeCode.UInt32: return uint_type; case TypeCode.UInt64: return ulong_type; } } internal static string PredefinedTypeObjectToString (object obj) { Type type = obj.GetType (); switch (Type.GetTypeCode (type)) { case TypeCode.Object: // other than System.Object case TypeCode.Empty: default: if (type == typeof (object)) return String.Empty; if (type == typeof (Guid)) return XmlConvert.ToString ((Guid) obj); if (type == typeof (TimeSpan)) return XmlConvert.ToString ((TimeSpan) obj); if (type == typeof (byte [])) return Convert.ToBase64String ((byte []) obj); if (type == typeof (Uri)) return ((Uri) obj).ToString (); throw new Exception ("Internal error: missing predefined type serialization for type " + type.FullName); case TypeCode.DBNull: // predefined, but not primitive return String.Empty; case TypeCode.Boolean: return XmlConvert.ToString ((bool) obj); case TypeCode.Byte: return XmlConvert.ToString ((int)((byte) obj)); case TypeCode.Char: return XmlConvert.ToString ((uint) (char) obj); case TypeCode.DateTime: return XmlConvert.ToString ((DateTime) obj, XmlDateTimeSerializationMode.RoundtripKind); case TypeCode.Decimal: return XmlConvert.ToString ((decimal) obj); case TypeCode.Double: return XmlConvert.ToString ((double) obj); case TypeCode.Int16: return XmlConvert.ToString ((short) obj); case TypeCode.Int32: return XmlConvert.ToString ((int) obj); case TypeCode.Int64: return XmlConvert.ToString ((long) obj); case TypeCode.SByte: return XmlConvert.ToString ((sbyte) obj); case TypeCode.Single: return XmlConvert.ToString ((float) obj); case TypeCode.String: return (string) obj; case TypeCode.UInt16: return XmlConvert.ToString ((int) (ushort) obj); case TypeCode.UInt32: return XmlConvert.ToString ((uint) obj); case TypeCode.UInt64: return XmlConvert.ToString ((ulong) obj); } } internal static Type GetPrimitiveTypeFromName (string name) { switch (name) { case "anyURI": return typeof (Uri); case "boolean": return typeof (bool); case "base64Binary": return typeof (byte []); case "dateTime": return typeof (DateTime); case "duration": return typeof (TimeSpan); case "QName": return typeof (QName); case "decimal": return typeof (decimal); case "double": return typeof (double); case "float": return typeof (float); case "byte": return typeof (sbyte); case "short": return typeof (short); case "int": return typeof (int); case "long": return typeof (long); case "unsignedByte": return typeof (byte); case "unsignedShort": return typeof (ushort); case "unsignedInt": return typeof (uint); case "unsignedLong": return typeof (ulong); case "string": return typeof (string); case "anyType": return typeof (object); case "guid": return typeof (Guid); case "char": return typeof (char); default: return null; } } internal static object PredefinedTypeStringToObject (string s, string name, XmlReader reader) { switch (name) { case "anyURI": return new Uri(s,UriKind.RelativeOrAbsolute); case "boolean": return XmlConvert.ToBoolean (s); case "base64Binary": return Convert.FromBase64String (s); case "dateTime": return XmlConvert.ToDateTime (s, XmlDateTimeSerializationMode.RoundtripKind); case "duration": return XmlConvert.ToTimeSpan (s); case "QName": int idx = s.IndexOf (':'); string l = idx < 0 ? s : s.Substring (idx + 1); return idx < 0 ? new QName (l) : new QName (l, reader.LookupNamespace ( s.Substring (0, idx))); case "decimal": return XmlConvert.ToDecimal (s); case "double": return XmlConvert.ToDouble (s); case "float": return XmlConvert.ToSingle (s); case "byte": return XmlConvert.ToSByte (s); case "short": return XmlConvert.ToInt16 (s); case "int": return XmlConvert.ToInt32 (s); case "long": return XmlConvert.ToInt64 (s); case "unsignedByte": return XmlConvert.ToByte (s); case "unsignedShort": return XmlConvert.ToUInt16 (s); case "unsignedInt": return XmlConvert.ToUInt32 (s); case "unsignedLong": return XmlConvert.ToUInt64 (s); case "string": return s; case "guid": return XmlConvert.ToGuid (s); case "anyType": return s; case "char": return (char) XmlConvert.ToUInt32 (s); default: throw new Exception ("Unanticipated primitive type: " + name); } } List contracts = new List (); public KnownTypeCollection () { } protected override void ClearItems () { base.Clear (); } protected override void InsertItem (int index, Type type) { if (TryRegister (type)) base.InsertItem (index, type); } // FIXME: it could remove other types' dependencies. protected override void RemoveItem (int index) { Type t = base [index]; List l = new List (); foreach (SerializationMap m in contracts) { if (m.RuntimeType == t) l.Add (m); } foreach (SerializationMap m in l) { contracts.Remove (m); base.RemoveItem (index); } } protected override void SetItem (int index, Type type) { if (index == Count) InsertItem (index, type); else { RemoveItem (index); if (TryRegister (type)) base.InsertItem (index - 1, type); } } internal SerializationMap FindUserMap (QName qname) { for (int i = 0; i < contracts.Count; i++) if (qname == contracts [i].XmlName) return contracts [i]; return null; } internal Type GetSerializedType (Type type) { Type element = GetCollectionElementType (type); if (element == null) return type; QName name = GetQName (type); var map = FindUserMap (name); if (map != null) return map.RuntimeType; return type; } internal SerializationMap FindUserMap (Type type) { for (int i = 0; i < contracts.Count; i++) if (type == contracts [i].RuntimeType) return contracts [i]; return null; } internal QName GetQName (Type type) { if (IsPrimitiveNotEnum (type)) return GetPrimitiveTypeName (type); SerializationMap map = FindUserMap (type); if (map != null) // already mapped. return map.XmlName; if (type.IsEnum) return GetEnumQName (type); QName qname = GetContractQName (type); if (qname != null) return qname; if (type.GetInterface ("System.Xml.Serialization.IXmlSerializable") != null) //FIXME: Reusing GetSerializableQName here, since we just //need name of the type.. return GetSerializableQName (type); qname = GetCollectionContractQName (type); if (qname != null) return qname; Type element = GetCollectionElementType (type); if (element != null) return GetCollectionQName (element); if (GetAttribute (type) != null) return GetSerializableQName (type); // FIXME: it needs in-depth check. return QName.Empty; } QName GetContractQName (Type type) { var a = GetAttribute (type); return a == null ? null : GetContractQName (type, a.Name, a.Namespace); } QName GetCollectionContractQName (Type type) { var a = GetAttribute (type); return a == null ? null : GetContractQName (type, a.Name, a.Namespace); } internal static QName GetContractQName (Type type, string name, string ns) { if (name == null) { // FIXME: there could be decent ways to get // the same result... name = type.Namespace == null || type.Namespace.Length == 0 ? type.Name : type.FullName.Substring (type.Namespace.Length + 1).Replace ('+', '.'); if (type.IsGenericType) { name = name.Substring (0, name.IndexOf ('`')) + "Of"; foreach (var t in type.GetGenericArguments ()) name += t.Name; // FIXME: check namespaces too } } if (ns == null) ns = DefaultClrNamespaceBase + type.Namespace; return new QName (name, ns); } private QName GetEnumQName (Type type) { string name = null, ns = null; if (!type.IsEnum) return null; var dca = GetAttribute (type); if (dca != null) { ns = dca.Namespace; name = dca.Name; } if (ns == null) ns = DefaultClrNamespaceBase + type.Namespace; if (name == null) name = type.Namespace == null ? type.Name : type.FullName.Substring (type.Namespace.Length + 1).Replace ('+', '.'); return new QName (name, ns); } private QName GetCollectionQName (Type element) { QName eqname = GetQName (element); string ns = eqname.Namespace; if (eqname.Namespace == MSSimpleNamespace) //Arrays of Primitive types ns = MSArraysNamespace; return new QName ( "ArrayOf" + XmlConvert.EncodeLocalName (eqname.Name), ns); } private QName GetSerializableQName (Type type) { string xmlName = type.Name; if (type.IsGenericType) { xmlName = xmlName.Substring (0, xmlName.IndexOf ('`')) + "Of"; foreach (var t in type.GetGenericArguments ()) xmlName += GetQName (t).Name; // FIXME: check namespaces too } string xmlNamespace = DefaultClrNamespaceBase + type.Namespace; var x = GetAttribute (type); if (x != null) { xmlName = x.ElementName; xmlNamespace = x.Namespace; } return new QName (XmlConvert.EncodeLocalName (xmlName), xmlNamespace); } internal bool IsPrimitiveNotEnum (Type type) { if (type.IsEnum) return false; if (Type.GetTypeCode (type) != TypeCode.Object) // explicitly primitive return true; if (type == typeof (Guid) || type == typeof (object) || type == typeof(TimeSpan) || type == typeof(byte[]) || type==typeof(Uri)) // special primitives return true; // nullable if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>)) return IsPrimitiveNotEnum (type.GetGenericArguments () [0]); return false; } internal bool TryRegister (Type type) { // exclude predefined maps if (IsPrimitiveNotEnum (type)) return false; if (FindUserMap (type) != null) return false; if (RegisterEnum (type) != null) return true; if (RegisterContract (type) != null) return true; if (RegisterIXmlSerializable (type) != null) return true; if (RegisterDictionary (type) != null) return true; if (RegisterCollectionContract (type) != null) return true; if (RegisterCollection (type) != null) return true; if (GetAttribute (type) != null) { RegisterSerializable (type); return true; } RegisterDefaultTypeMap (type); return true; } internal static Type GetCollectionElementType (Type type) { if (type.IsArray) return type.GetElementType (); Type [] ifaces = type.GetInterfaces (); foreach (Type i in ifaces) if (i.IsGenericType && i.GetGenericTypeDefinition ().Equals (typeof (ICollection<>))) return i.GetGenericArguments () [0]; foreach (Type i in ifaces) if (i == typeof (IList)) return typeof (object); return null; } internal T GetAttribute (MemberInfo mi) where T : Attribute { object [] atts = mi.GetCustomAttributes (typeof (T), false); return atts.Length == 0 ? null : (T) atts [0]; } private CollectionContractTypeMap RegisterCollectionContract (Type type) { var cdca = GetAttribute (type); if (cdca == null) return null; Type element = GetCollectionElementType (type); if (element == null) throw new InvalidOperationException (String.Format ("Type '{0}' is marked as collection contract, but it is not a collection", type)); TryRegister (element); // must be registered before the name conflict check. QName qname = GetCollectionContractQName (type); CheckStandardQName (qname); if (FindUserMap (qname) != null) 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)); var ret = new CollectionContractTypeMap (type, cdca, element, qname, this); contracts.Add (ret); return ret; } private CollectionTypeMap RegisterCollection (Type type) { Type element = GetCollectionElementType (type); if (element == null) return null; TryRegister (element); QName qname = GetCollectionQName (element); var map = FindUserMap (qname); if (map != null) { var cmap = map as CollectionTypeMap; if (cmap == null || cmap.RuntimeType != type) 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)); return cmap; } CollectionTypeMap ret = new CollectionTypeMap (type, element, qname, this); contracts.Add (ret); return ret; } static bool TypeImplementsIDictionary (Type type) { foreach (var iface in type.GetInterfaces ()) if (iface == typeof (IDictionary) || (iface.IsGenericType && iface.GetGenericTypeDefinition () == typeof (IDictionary<,>))) return true; return false; } // it also supports contract-based dictionary. private DictionaryTypeMap RegisterDictionary (Type type) { if (!TypeImplementsIDictionary (type)) return null; var cdca = GetAttribute (type); DictionaryTypeMap ret = new DictionaryTypeMap (type, cdca, this); if (FindUserMap (ret.XmlName) != null) 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)); contracts.Add (ret); TryRegister (ret.KeyType); TryRegister (ret.ValueType); return ret; } private SerializationMap RegisterSerializable (Type type) { QName qname = GetSerializableQName (type); if (FindUserMap (qname) != null) throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname)); SharedTypeMap ret = new SharedTypeMap (type, qname, this); contracts.Add (ret); return ret; } private SerializationMap RegisterIXmlSerializable (Type type) { if (type.GetInterface ("System.Xml.Serialization.IXmlSerializable") == null) return null; QName qname = GetSerializableQName (type); if (FindUserMap (qname) != null) throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname)); XmlSerializableMap ret = new XmlSerializableMap (type, qname, this); contracts.Add (ret); return ret; } void CheckStandardQName (QName qname) { switch (qname.Namespace) { case XmlSchema.Namespace: case XmlSchema.InstanceNamespace: case MSSimpleNamespace: case MSArraysNamespace: throw new InvalidOperationException (String.Format ("Namespace {0} is reserved and cannot be used for user serialization", qname.Namespace)); } } private SharedContractMap RegisterContract (Type type) { QName qname = GetContractQName (type); if (qname == null) return null; CheckStandardQName (qname); if (FindUserMap (qname) != null) throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname)); SharedContractMap ret = new SharedContractMap (type, qname, this); contracts.Add (ret); ret.Initialize (); object [] attrs = type.GetCustomAttributes (typeof (KnownTypeAttribute), true); for (int i = 0; i < attrs.Length; i++) { KnownTypeAttribute kt = (KnownTypeAttribute) attrs [i]; TryRegister (kt.Type); } return ret; } DefaultTypeMap RegisterDefaultTypeMap (Type type) { DefaultTypeMap ret = new DefaultTypeMap (type, this); contracts.Add (ret); return ret; } private EnumMap RegisterEnum (Type type) { QName qname = GetEnumQName (type); if (qname == null) return null; if (FindUserMap (qname) != null) throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname)); EnumMap ret = new EnumMap (type, qname, this); contracts.Add (ret); return ret; } } } #endif