// 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
exists (and raises InvalidOperationException if required).
*/
+
internal static class TypeExtensions
{
+#if !NET_4_5
public static T GetCustomAttribute<T> (this MemberInfo type, bool inherit)
{
var arr = type.GetCustomAttributes (typeof (T), inherit);
return arr != null && arr.Length == 1 ? (T) arr [0] : default (T);
}
+#endif
+ public static IEnumerable<Type> GetInterfacesOrSelfInterface (this Type type)
+ {
+ if (type.IsInterface)
+ yield return type;
+ foreach (var t in type.GetInterfaces ())
+ yield return t;
+ }
+
+ public static bool ImplementsInterface (this Type type, Type iface)
+ {
+ foreach (var t in type.GetInterfacesOrSelfInterface ()) {
+ if (t == iface)
+ return true;
+ }
+
+ var baseType = type.BaseType;
+ if (baseType != null)
+ return baseType.ImplementsInterface (iface);
+
+ return false;
+ }
}
internal sealed class KnownTypeCollection : Collection<Type>
"http://schemas.microsoft.com/2003/10/Serialization/Arrays";
internal const string DefaultClrNamespaceBase =
"http://schemas.datacontract.org/2004/07/";
+ internal const string DefaultClrNamespaceSystem =
+ "http://schemas.datacontract.org/2004/07/System";
+
static QName any_type, bool_type,
byte_type, date_type, decimal_type, double_type,
// custom in ms nsURI schema
char_type, guid_type,
// not in ms nsURI schema
- dbnull_type;
+ dbnull_type, date_time_offset_type;
+
+ // 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 :-(
+ static Dictionary<string,Type> xs_predefined_types = new Dictionary<string,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);
guid_type = new QName ("guid", s);
char_type = new QName ("char", s);
- dbnull_type = new QName ("DBNull", MSSimpleNamespace + "System");
+ dbnull_type = new QName ("DBNull", DefaultClrNamespaceBase + "System");
+ date_time_offset_type = new QName ("DateTimeOffset", DefaultClrNamespaceBase + "System");
+
+ xs_predefined_types.Add ("string", typeof (string));
+ xs_predefined_types.Add ("boolean", typeof (bool));
+ xs_predefined_types.Add ("float", typeof (float));
+ xs_predefined_types.Add ("double", typeof (double));
+ xs_predefined_types.Add ("decimal", typeof (decimal));
+ xs_predefined_types.Add ("duration", typeof (TimeSpan));
+ xs_predefined_types.Add ("dateTime", typeof (DateTime));
+ xs_predefined_types.Add ("date", typeof (DateTime));
+ xs_predefined_types.Add ("time", typeof (DateTime));
+ xs_predefined_types.Add ("gYearMonth", typeof (DateTime));
+ xs_predefined_types.Add ("gYear", typeof (DateTime));
+ xs_predefined_types.Add ("gMonthDay", typeof (DateTime));
+ xs_predefined_types.Add ("gDay", typeof (DateTime));
+ xs_predefined_types.Add ("gMonth", typeof (DateTime));
+ xs_predefined_types.Add ("hexBinary", typeof (byte []));
+ xs_predefined_types.Add ("base64Binary", typeof (byte []));
+ xs_predefined_types.Add ("anyURI", typeof (Uri));
+ xs_predefined_types.Add ("QName", typeof (QName));
+ xs_predefined_types.Add ("NOTATION", typeof (string));
+
+ xs_predefined_types.Add ("normalizedString", typeof (string));
+ xs_predefined_types.Add ("token", typeof (string));
+ xs_predefined_types.Add ("language", typeof (string));
+ xs_predefined_types.Add ("IDREFS", typeof (string []));
+ xs_predefined_types.Add ("ENTITIES", typeof (string []));
+ xs_predefined_types.Add ("NMTOKEN", typeof (string));
+ xs_predefined_types.Add ("NMTOKENS", typeof (string []));
+ xs_predefined_types.Add ("Name", typeof (string));
+ xs_predefined_types.Add ("NCName", typeof (string));
+ xs_predefined_types.Add ("ID", typeof (string));
+ xs_predefined_types.Add ("IDREF", typeof (string));
+ xs_predefined_types.Add ("ENTITY", typeof (string));
+
+ xs_predefined_types.Add ("integer", typeof (decimal));
+ xs_predefined_types.Add ("nonPositiveInteger", typeof (int));
+ xs_predefined_types.Add ("negativeInteger", typeof (int));
+ xs_predefined_types.Add ("long", typeof (long));
+ xs_predefined_types.Add ("int", typeof (int));
+ xs_predefined_types.Add ("short", typeof (short));
+ xs_predefined_types.Add ("byte", typeof (sbyte));
+ xs_predefined_types.Add ("nonNegativeInteger", typeof (decimal));
+ xs_predefined_types.Add ("unsignedLong", typeof (ulong));
+ xs_predefined_types.Add ("unsignedInt", typeof (uint));
+ xs_predefined_types.Add ("unsignedShort", typeof (ushort));
+ xs_predefined_types.Add ("unsignedByte", typeof (byte));
+ xs_predefined_types.Add ("positiveInteger", typeof (decimal));
+
+ xs_predefined_types.Add ("anyType", typeof (object));
}
// FIXME: find out how QName and guid are processed
return base64_type;
if (type == typeof (Uri))
return any_uri_type;
+ if (type == typeof (DateTimeOffset))
+ return date_time_offset_type;
return QName.Empty;
case TypeCode.Boolean:
return bool_type;
}
}
- // FIXME: xsd types and ms serialization types should be differentiated.
- internal static Type GetPrimitiveTypeFromName (string name)
+ internal static Type GetPrimitiveTypeFromName (QName 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;
+ switch (name.Namespace) {
+ case DefaultClrNamespaceSystem:
+ switch (name.Name) {
+ case "DBNull":
+ return typeof (DBNull);
+ case "DateTimeOffset":
+ return typeof (DateTimeOffset);
+ }
+ break;
+ case XmlSchema.Namespace:
+ return xs_predefined_types.FirstOrDefault (p => p.Key == name.Name).Value;
+ case MSSimpleNamespace:
+ switch (name.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);
+ }
+ break;
}
+ return null;
}
// FIXME: it could remove other types' dependencies.
protected override void RemoveItem (int index)
+ {
+ lock (this)
+ DoRemoveItem (index);
+ }
+
+ void DoRemoveItem (int index)
{
Type t = base [index];
List<SerializationMap> l = new List<SerializationMap> ();
base.InsertItem (index - 1, type);
}
+ internal SerializationMap FindUserMap (Type type)
+ {
+ lock (this) {
+ for (int i = 0; i < contracts.Count; i++)
+ if (type == contracts [i].RuntimeType)
+ return contracts [i];
+ return null;
+ }
+ }
+
internal SerializationMap FindUserMap (QName qname)
{
- return contracts.FirstOrDefault (c => c.XmlName == qname);
+ lock (this)
+ return contracts.FirstOrDefault (c => c.XmlName == qname);
+ }
+
+ internal SerializationMap FindUserMap (QName qname, Type type)
+ {
+ lock (this)
+ return contracts.FirstOrDefault (c => c.XmlName == qname && c.RuntimeType == type);
}
internal Type GetSerializedType (Type type)
{
+ if (IsPrimitiveNotEnum (type))
+ return type;
Type element = GetCollectionElementType (type);
if (element == null)
return type;
QName name = GetQName (type);
- var map = FindUserMap (name);
+ var map = FindUserMap (name, type);
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)
{
SerializationMap map = FindUserMap (type);
if (map != null)
// already mapped.
- return map.XmlName;
+ return map.XmlName;
return GetStaticQName (type);
}
return qname;
if (type.GetInterface ("System.Xml.Serialization.IXmlSerializable") != null)
- //FIXME: Reusing GetSerializableQName here, since we just
+ //FIXME: Reusing GetSerializableQName here, since we just
//need name of the type..
return GetSerializableQName (type);
return qname;
Type element = GetCollectionElementType (type);
- if (element != null)
- return GetCollectionQName (element);
+ if (element != null) {
+ if (type.IsInterface || IsCustomCollectionType (type, element))
+ return GetCollectionQName (element);
+ }
if (GetAttribute<SerializableAttribute> (type) != null)
return GetSerializableQName (type);
{
if (name == null)
name = GetDefaultName (type);
+ else if (type.IsGenericType) {
+ var args = type.GetGenericArguments ();
+ for (int i = 0; i < args.Length; i++)
+ name = name.Replace ("{" + i + "}", GetStaticQName (args [i]).Name);
+ }
+
if (ns == null)
ns = GetDefaultNamespace (type);
return new QName (name, ns);
internal static string GetDefaultName (Type type)
{
- // FIXME: there could be decent ways to get
+ // FIXME: there could be decent ways to get
// the same result...
string name = type.Namespace == null || type.Namespace.Length == 0 ? type.Name : type.FullName.Substring (type.Namespace.Length + 1).Replace ('+', '.');
if (type.IsGenericType) {
static QName GetCollectionQName (Type element)
{
QName eqname = GetStaticQName (element);
-
+
string ns = eqname.Namespace;
if (eqname.Namespace == MSSimpleNamespace)
//Arrays of Primitive types
static QName GetSerializableQName (Type type)
{
-#if !NET_2_1
+#if !MOONLIGHT
// First, check XmlSchemaProviderAttribute and try GetSchema() to see if it returns a schema in the expected format.
var xpa = type.GetCustomAttribute<XmlSchemaProviderAttribute> (true);
if (xpa != null) {
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
+ if (type == typeof (Guid) || type == typeof (object) || type == typeof(TimeSpan) || type == typeof(byte[]) || type == typeof(Uri) || type == typeof(DateTimeOffset)) // special primitives
return true;
+#if !MOONLIGHT
+ // DOM nodes
+ if (type == typeof (XmlElement) || type == typeof (XmlNode []))
+ return true;
+#endif
// nullable
if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>))
return IsPrimitiveNotEnum (type.GetGenericArguments () [0]);
}
internal bool TryRegister (Type type)
+ {
+ lock (this) {
+ return DoTryRegister (type);
+ }
+ }
+
+ bool DoTryRegister (Type type)
{
// exclude predefined maps
if (ShouldNotRegister (type))
{
if (type.IsArray)
return type.GetElementType ();
-
- Type [] ifaces = type.GetInterfaces ();
+ var ifaces = type.GetInterfacesOrSelfInterface ();
foreach (Type i in ifaces)
- if (i.IsGenericType && i.GetGenericTypeDefinition ().Equals (typeof (ICollection<>)))
+ if (i.IsGenericType && i.GetGenericTypeDefinition ().Equals (typeof (IEnumerable<>)))
return i.GetGenericArguments () [0];
foreach (Type i in ifaces)
- if (i == typeof (IList))
+ if (i == typeof (IEnumerable))
return typeof (object);
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));
+ throw new InvalidDataContractException (String.Format ("Type '{0}' is marked as collection contract, but it is not a collection", type));
+ if (type.GetMethod ("Add", new Type[] { element }) == null)
+ throw new InvalidDataContractException (String.Format ("Type '{0}' is marked as collection contract, but missing a public \"Add\" method", 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 map = FindUserMap (qname, type);
+ if (map != null) {
+ var cmap = map as CollectionContractTypeMap;
+ if (cmap == null) // The runtime type may still differ (between array and other IList; see bug #670560)
+ 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);
TryRegister (element);
+ /*
+ * To qualify as a custom collection type, a type must have
+ * a public parameterless constructor and an "Add" method
+ * with the correct parameter type in addition to implementing
+ * one of the collection interfaces.
+ *
+ */
+
+ if (!type.IsArray && type.IsClass && !IsCustomCollectionType (type, element))
+ return null;
+
QName qname = GetCollectionQName (element);
- var map = FindUserMap (qname);
+ var map = FindUserMap (qname, element);
if (map != null) {
var cmap = map as CollectionTypeMap;
- if (cmap == null || cmap.RuntimeType != type)
+ if (cmap == null) // The runtime type may still differ (between array and other IList; see bug #670560)
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;
}
return ret;
}
+ static bool IsCustomCollectionType (Type type, Type elementType)
+ {
+ if (!type.IsClass)
+ return false;
+ if (type.GetConstructor (new Type [0]) == null)
+ return false;
+ if (type.GetMethod ("Add", new Type[] { elementType }) == null)
+ return false;
+
+ return true;
+ }
+
+ internal static bool IsInterchangeableCollectionType (Type contractType, Type graphType,
+ out QName collectionQName)
+ {
+ collectionQName = null;
+ if (GetAttribute<CollectionDataContractAttribute> (contractType) != null)
+ return false;
+
+ var type = contractType;
+ if (type.IsGenericType)
+ type = type.GetGenericTypeDefinition ();
+
+ var elementType = GetCollectionElementType (contractType);
+ if (elementType == null)
+ return false;
+
+ if (contractType.IsArray) {
+ if (!graphType.IsArray || !elementType.Equals (graphType.GetElementType ()))
+ throw new InvalidCastException (String.Format ("Type '{0}' cannot be converted into '{1}'.", graphType.GetElementType (), elementType));
+ } else if (!contractType.IsInterface) {
+ if (GetAttribute<SerializableAttribute> (contractType) == null)
+ return false;
+
+ var graphElementType = GetCollectionElementType (graphType);
+ if (elementType != graphElementType)
+ return false;
+
+ if (!IsCustomCollectionType (contractType, elementType))
+ return false;
+ } else if (type.Equals (typeof (IEnumerable)) || type.Equals (typeof (IList)) ||
+ type.Equals (typeof (ICollection))) {
+ if (!graphType.ImplementsInterface (contractType))
+ return false;
+ } else if (type.Equals (typeof (IEnumerable<>)) || type.Equals (typeof (IList<>)) ||
+ type.Equals (typeof (ICollection<>))) {
+ var graphElementType = GetCollectionElementType (graphType);
+ if (graphElementType != elementType)
+ throw new InvalidCastException (String.Format (
+ "Cannot convert type '{0}' into '{1}'.", graphType, contractType));
+
+ if (!graphType.ImplementsInterface (contractType))
+ return false;
+ } else {
+ return false;
+ }
+
+ collectionQName = GetCollectionQName (elementType);
+ return true;
+ }
+
+ static bool ImplementsInterface (Type type, Type iface)
+ {
+ foreach (var i in type.GetInterfacesOrSelfInterface ())
+ if (iface == i)
+ return true;
+
+ return false;
+ }
+
+
+ static bool TypeImplementsIEnumerable (Type type)
+ {
+ foreach (var iface in type.GetInterfacesOrSelfInterface ())
+ if (iface == typeof (IEnumerable) || (iface.IsGenericType && iface.GetGenericTypeDefinition () == typeof (IEnumerable<>)))
+ return true;
+
+ return false;
+ }
+
static bool TypeImplementsIDictionary (Type type)
{
- foreach (var iface in type.GetInterfaces ())
+ foreach (var iface in type.GetInterfacesOrSelfInterface ())
if (iface == typeof (IDictionary) || (iface.IsGenericType && iface.GetGenericTypeDefinition () == typeof (IDictionary<,>)))
return true;
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);
+ var map = FindUserMap (ret.XmlName, type);
+ if (map != null) {
+ var dmap = map as DictionaryTypeMap;
+ if (dmap == null) // The runtime type may still differ (between array and other IList; see bug #670560)
+ 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);
+
return ret;
}
{
QName qname = GetSerializableQName (type);
- if (FindUserMap (qname) != null)
+ if (FindUserMap (qname, type) != null)
throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
SharedTypeMap ret = new SharedTypeMap (type, qname, this);
QName qname = GetSerializableQName (type);
- if (FindUserMap (qname) != null)
+ if (FindUserMap (qname, type) != null)
throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
XmlSerializableMap ret = new XmlSerializableMap (type, qname, this);
if (qname == null)
return null;
CheckStandardQName (qname);
- if (FindUserMap (qname) != null)
+ if (FindUserMap (qname, type) != null)
throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
SharedContractMap ret = new SharedContractMap (type, qname, this);
object [] attrs = type.GetCustomAttributes (typeof (KnownTypeAttribute), true);
for (int i = 0; i < attrs.Length; i++) {
KnownTypeAttribute kt = (KnownTypeAttribute) attrs [i];
- TryRegister (kt.Type);
+ foreach (var t in kt.GetTypes (type))
+ TryRegister (t);
}
return ret;
if (qname == null)
return null;
- if (FindUserMap (qname) != null)
+ if (FindUserMap (qname, type) != null)
throw new InvalidOperationException (String.Format ("There is already a registered type for XML name {0}", qname));
EnumMap ret =