// // Copyright (C) 2010 Novell Inc. http://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. // using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; using System.Windows.Markup; using System.Xaml.Schema; using System.Xml.Serialization; namespace System.Xaml { public class XamlType : IEquatable { public XamlType (Type underlyingType, XamlSchemaContext schemaContext) : this (underlyingType, schemaContext, null) { } // static readonly Type [] predefined_types = { // typeof (XData), typeof (Uri), typeof (TimeSpan), typeof (PropertyDefinition), typeof (MemberDefinition), typeof (Reference) // }; public XamlType (Type underlyingType, XamlSchemaContext schemaContext, XamlTypeInvoker invoker) : this (schemaContext, invoker) { if (underlyingType == null) throw new ArgumentNullException ("underlyingType"); type = underlyingType; underlying_type = type; XamlType xt; if (XamlLanguage.InitializingTypes) { // These are special. Only XamlLanguage members are with shorthand name. if (type == typeof (PropertyDefinition)) Name = "Property"; else if (type == typeof (MemberDefinition)) Name = "Member"; else Name = GetXamlName (type); PreferredXamlNamespace = XamlLanguage.Xaml2006Namespace; } else if ((xt = XamlLanguage.AllTypes.FirstOrDefault (t => t.UnderlyingType == type)) != null) { Name = xt.Name; PreferredXamlNamespace = XamlLanguage.Xaml2006Namespace; } else { Name = GetXamlName (type); PreferredXamlNamespace = schemaContext.GetXamlNamespace (type.Namespace) ?? String.Format ("clr-namespace:{0};assembly={1}", type.Namespace, type.Assembly.GetName ().Name); } if (type.IsGenericType) { TypeArguments = new List (); foreach (var gta in type.GetGenericArguments ()) TypeArguments.Add (schemaContext.GetXamlType (gta)); } } public XamlType (string unknownTypeNamespace, string unknownTypeName, IList typeArguments, XamlSchemaContext schemaContext) : this (schemaContext, null) { if (unknownTypeNamespace == null) throw new ArgumentNullException ("unknownTypeNamespace"); if (unknownTypeName == null) throw new ArgumentNullException ("unknownTypeName"); if (schemaContext == null) throw new ArgumentNullException ("schemaContext"); type = typeof (object); Name = unknownTypeName; PreferredXamlNamespace = unknownTypeNamespace; TypeArguments = typeArguments != null && typeArguments.Count == 0 ? null : typeArguments; // explicit_ns = unknownTypeNamespace; } protected XamlType (string typeName, IList typeArguments, XamlSchemaContext schemaContext) : this (String.Empty, typeName, typeArguments, schemaContext) { } XamlType (XamlSchemaContext schemaContext, XamlTypeInvoker invoker) { if (schemaContext == null) throw new ArgumentNullException ("schemaContext"); SchemaContext = schemaContext; this.invoker = invoker ?? new XamlTypeInvoker (this); } Type type, underlying_type; // string explicit_ns; // populated properties XamlType base_type; XamlTypeInvoker invoker; internal EventHandler SetMarkupExtensionHandler { get { return LookupSetMarkupExtensionHandler (); } } internal EventHandler SetTypeConverterHandler { get { return LookupSetTypeConverterHandler (); } } public IList AllowedContentTypes { get { return LookupAllowedContentTypes (); } } public XamlType BaseType { get { return LookupBaseType (); } } public bool ConstructionRequiresArguments { get { return LookupConstructionRequiresArguments (); } } public XamlMember ContentProperty { get { return LookupContentProperty (); } } public IList ContentWrappers { get { return LookupContentWrappers (); } } public XamlValueConverter DeferringLoader { get { return LookupDeferringLoader (); } } public XamlTypeInvoker Invoker { get { return LookupInvoker (); } } public bool IsAmbient { get { return LookupIsAmbient (); } } public bool IsArray { get { return LookupCollectionKind () == XamlCollectionKind.Array; } } // it somehow treats array as not a collection... public bool IsCollection { get { return LookupCollectionKind () == XamlCollectionKind.Collection; } } public bool IsConstructible { get { return LookupIsConstructible (); } } public bool IsDictionary { get { return LookupCollectionKind () == XamlCollectionKind.Dictionary; } } public bool IsGeneric { get { return type.IsGenericType; } } public bool IsMarkupExtension { get { return LookupIsMarkupExtension (); } } public bool IsNameScope { get { return LookupIsNameScope (); } } public bool IsNameValid { get { return XamlLanguage.IsValidXamlName (Name); } } public bool IsNullable { get { return LookupIsNullable (); } } public bool IsPublic { get { return LookupIsPublic (); } } public bool IsUnknown { get { return LookupIsUnknown (); } } public bool IsUsableDuringInitialization { get { return LookupUsableDuringInitialization (); } } public bool IsWhitespaceSignificantCollection { get { return LookupIsWhitespaceSignificantCollection (); } } public bool IsXData { get { return LookupIsXData (); } } public XamlType ItemType { get { return LookupItemType (); } } public XamlType KeyType { get { return LookupKeyType (); } } public XamlType MarkupExtensionReturnType { get { return LookupMarkupExtensionReturnType (); } } public string Name { get; private set; } public string PreferredXamlNamespace { get; private set; } public XamlSchemaContext SchemaContext { get; private set; } public bool TrimSurroundingWhitespace { get { return LookupTrimSurroundingWhitespace (); } } public IList TypeArguments { get; private set; } public XamlValueConverter TypeConverter { get { return LookupTypeConverter (); } } public Type UnderlyingType { get { return LookupUnderlyingType (); } } public XamlValueConverter ValueSerializer { get { return LookupValueSerializer (); } } internal string GetInternalXmlName () { if (IsMarkupExtension && Name.EndsWith ("Extension", StringComparison.Ordinal)) return Name.Substring (0, Name.Length - 9); var stn = XamlLanguage.SpecialNames.FirstOrDefault (s => s.Type == this); return stn != null ? stn.Name : Name; } public static bool operator == (XamlType left, XamlType right) { return IsNull (left) ? IsNull (right) : left.Equals (right); } static bool IsNull (XamlType a) { return Object.ReferenceEquals (a, null); } public static bool operator != (XamlType left, XamlType right) { return !(left == right); } public bool Equals (XamlType other) { // It does not compare XamlSchemaContext. return !IsNull (other) && UnderlyingType == other.UnderlyingType && Name == other.Name && PreferredXamlNamespace == other.PreferredXamlNamespace && TypeArguments.ListEquals (other.TypeArguments); } public override bool Equals (object obj) { var a = obj as XamlType; return Equals (a); } public override int GetHashCode () { if (UnderlyingType != null) return UnderlyingType.GetHashCode (); int x = Name.GetHashCode () << 7 + PreferredXamlNamespace.GetHashCode (); if (TypeArguments != null) foreach (var t in TypeArguments) x = t.GetHashCode () + x << 5; return x; } public override string ToString () { return new XamlTypeName (this).ToString (); //return String.IsNullOrEmpty (PreferredXamlNamespace) ? Name : String.Concat ("{", PreferredXamlNamespace, "}", Name); } public virtual bool CanAssignTo (XamlType xamlType) { if (this.UnderlyingType == null) return xamlType == XamlLanguage.Object; var ut = xamlType.UnderlyingType ?? typeof (object); return ut.IsAssignableFrom (UnderlyingType); } public XamlMember GetAliasedProperty (XamlDirective directive) { return LookupAliasedProperty (directive); } public ICollection GetAllAttachableMembers () { return new List (LookupAllAttachableMembers ()); } public ICollection GetAllMembers () { return new List (LookupAllMembers ()); } public XamlMember GetAttachableMember (string name) { return LookupAttachableMember (name); } public XamlMember GetMember (string name) { return LookupMember (name, true); } public IList GetPositionalParameters (int parameterCount) { return LookupPositionalParameters (parameterCount); } public virtual IList GetXamlNamespaces () { throw new NotImplementedException (); /* this does not work like documented! if (explicit_ns != null) return new string [] {explicit_ns}; var l = SchemaContext.GetAllXamlNamespaces (); if (l != null) return new List (l); return new string [] {String.Empty}; */ } // lookups protected virtual XamlMember LookupAliasedProperty (XamlDirective directive) { if (directive == XamlLanguage.Key) { var a = this.GetCustomAttribute (); return a != null ? GetMember (a.Name) : null; } if (directive == XamlLanguage.Name) { var a = this.GetCustomAttribute (); return a != null ? GetMember (a.Name) : null; } if (directive == XamlLanguage.Uid) { var a = this.GetCustomAttribute (); return a != null ? GetMember (a.Name) : null; } if (directive == XamlLanguage.Lang) { var a = this.GetCustomAttribute (); return a != null ? GetMember (a.Name) : null; } return null; } protected virtual IEnumerable LookupAllAttachableMembers () { if (UnderlyingType == null) return BaseType != null ? BaseType.GetAllAttachableMembers () : empty_array; if (all_attachable_members_cache == null) { all_attachable_members_cache = new List (DoLookupAllAttachableMembers ()); all_attachable_members_cache.Sort (TypeExtensionMethods.CompareMembers); } return all_attachable_members_cache; } IEnumerable DoLookupAllAttachableMembers () { // based on http://msdn.microsoft.com/en-us/library/ff184560.aspx var bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; var gl = new Dictionary (); var sl = new Dictionary (); var al = new Dictionary (); //var rl = new Dictionary (); var nl = new List (); foreach (var mi in UnderlyingType.GetMethods (bf)) { string name = null; if (mi.Name.StartsWith ("Get", StringComparison.Ordinal)) { if (mi.ReturnType == typeof (void)) continue; var args = mi.GetParameters (); if (args.Length != 1) continue; name = mi.Name.Substring (3); gl.Add (name, mi); } else if (mi.Name.StartsWith ("Set", StringComparison.Ordinal)) { // looks like the return type is *ignored* //if (mi.ReturnType != typeof (void)) // continue; var args = mi.GetParameters (); if (args.Length != 2) continue; name = mi.Name.Substring (3); sl.Add (name, mi); } else if (mi.Name.EndsWith ("Handler", StringComparison.Ordinal)) { var args = mi.GetParameters (); if (args.Length != 2) continue; if (mi.Name.StartsWith ("Add", StringComparison.Ordinal)) { name = mi.Name.Substring (3, mi.Name.Length - 3 - 7); al.Add (name, mi); }/* else if (mi.Name.StartsWith ("Remove", StringComparison.Ordinal)) { name = mi.Name.Substring (6, mi.Name.Length - 6 - 7); rl.Add (name, mi); }*/ } if (name != null && !nl.Contains (name)) nl.Add (name); } foreach (var name in nl) { MethodInfo m; var g = gl.TryGetValue (name, out m) ? m : null; var s = sl.TryGetValue (name, out m) ? m : null; if (g != null || s != null) yield return new XamlMember (name, g, s, SchemaContext); var a = al.TryGetValue (name, out m) ? m : null; //var r = rl.TryGetValue (name, out m) ? m : null; if (a != null) yield return new XamlMember (name, a, SchemaContext); } } static readonly XamlMember [] empty_array = new XamlMember [0]; protected virtual IEnumerable LookupAllMembers () { if (UnderlyingType == null) return BaseType != null ? BaseType.GetAllMembers () : empty_array; if (all_members_cache == null) { all_members_cache = new List (DoLookupAllMembers ()); all_members_cache.Sort (TypeExtensionMethods.CompareMembers); } return all_members_cache; } List all_members_cache; List all_attachable_members_cache; IEnumerable DoLookupAllMembers () { // This is a hack that is likely required due to internal implementation difference in System.Uri. Our Uri has two readonly collection properties if (this == XamlLanguage.Uri) yield break; var bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; foreach (var pi in UnderlyingType.GetProperties (bf)) { if (pi.Name.Contains ('.')) // exclude explicit interface implementations. continue; if (pi.CanRead && (pi.CanWrite || IsCollectionType (pi.PropertyType) || typeof (IXmlSerializable).IsAssignableFrom (pi.PropertyType)) && pi.GetIndexParameters ().Length == 0) yield return new XamlMember (pi, SchemaContext); } foreach (var ei in UnderlyingType.GetEvents (bf)) yield return new XamlMember (ei, SchemaContext); } static bool IsPublicAccessor (MethodInfo mi) { return mi != null && mi.IsPublic; } bool IsCollectionType (Type type) { if (type == null) return false; var xt = SchemaContext.GetXamlType (type); return xt.LookupCollectionKind () != XamlCollectionKind.None; } protected virtual IList LookupAllowedContentTypes () { // the actual implementation is very different from what is documented :( return null; /* var l = new List (); if (ContentWrappers != null) l.AddRange (ContentWrappers); if (ContentProperty != null) l.Add (ContentProperty.Type); if (ItemType != null) l.Add (ItemType); return l.Count > 0 ? l : null; */ } protected virtual XamlMember LookupAttachableMember (string name) { return GetAllAttachableMembers ().FirstOrDefault (m => m.Name == name); } protected virtual XamlType LookupBaseType () { if (base_type == null) { if (UnderlyingType == null) base_type = SchemaContext.GetXamlType (typeof (object)); else base_type = type.BaseType == null || type.BaseType == typeof (object) ? null : SchemaContext.GetXamlType (type.BaseType); } return base_type; } // This implementation is not verified. (No place to use.) protected internal virtual XamlCollectionKind LookupCollectionKind () { if (UnderlyingType == null) return BaseType != null ? BaseType.LookupCollectionKind () : XamlCollectionKind.None; if (type.IsArray) return XamlCollectionKind.Array; if (type.ImplementsAnyInterfacesOf (typeof (IDictionary), typeof (IDictionary<,>))) return XamlCollectionKind.Dictionary; if (type.ImplementsAnyInterfacesOf (typeof (IList), typeof (ICollection<>))) return XamlCollectionKind.Collection; return XamlCollectionKind.None; } protected virtual bool LookupConstructionRequiresArguments () { if (UnderlyingType == null) return false; // not sure if it is required, but MemberDefinition return true while they are abstract and it makes no sense. if (UnderlyingType.IsAbstract) return true; // FIXME: probably some primitive types are treated as special. switch (Type.GetTypeCode (UnderlyingType)) { case TypeCode.String: return true; case TypeCode.Object: if (UnderlyingType == typeof (TimeSpan)) return false; break; default: return false; } return UnderlyingType.GetConstructor (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null) == null; } protected virtual XamlMember LookupContentProperty () { var a = this.GetCustomAttribute (); return a != null && a.Name != null ? GetMember (a.Name) : null; } protected virtual IList LookupContentWrappers () { if (GetCustomAttributeProvider () == null) return null; var arr = GetCustomAttributeProvider ().GetCustomAttributes (typeof (ContentWrapperAttribute), false); if (arr == null || arr.Length == 0) return null; var l = new XamlType [arr.Length]; for (int i = 0; i < l.Length; i++) l [i] = SchemaContext.GetXamlType (((ContentWrapperAttribute) arr [i]).ContentWrapper); return l; } internal ICustomAttributeProvider GetCustomAttributeProvider () { return LookupCustomAttributeProvider (); } protected internal virtual ICustomAttributeProvider LookupCustomAttributeProvider () { return UnderlyingType; } protected virtual XamlValueConverter LookupDeferringLoader () { throw new NotImplementedException (); } protected virtual XamlTypeInvoker LookupInvoker () { return invoker; } protected virtual bool LookupIsAmbient () { return this.GetCustomAttribute () != null; } // It is documented as if it were to reflect spec. section 5.2, // but the actual behavior shows it is *totally* wrong. // Here I have implemented this based on the nunit test results. sigh. protected virtual bool LookupIsConstructible () { if (UnderlyingType == null) return true; if (IsMarkupExtension) return true; if (UnderlyingType.IsAbstract) return false; if (!IsNameValid) return false; return true; } protected virtual bool LookupIsMarkupExtension () { return typeof (MarkupExtension).IsAssignableFrom (UnderlyingType); } protected virtual bool LookupIsNameScope () { return typeof (INameScope).IsAssignableFrom (UnderlyingType); } protected virtual bool LookupIsNullable () { return !type.IsValueType || type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>); } protected virtual bool LookupIsPublic () { return underlying_type == null || underlying_type.IsPublic || underlying_type.IsNestedPublic; } protected virtual bool LookupIsUnknown () { return UnderlyingType == null; } protected virtual bool LookupIsWhitespaceSignificantCollection () { // probably for unknown types, it should preserve whitespaces. return IsUnknown || this.GetCustomAttribute () != null; } protected virtual bool LookupIsXData () { return CanAssignTo (SchemaContext.GetXamlType (typeof (IXmlSerializable))); } protected virtual XamlType LookupItemType () { if (IsArray) return new XamlType (type.GetElementType (), SchemaContext); if (IsDictionary) { if (!IsGeneric) return new XamlType (typeof (object), SchemaContext); return new XamlType (type.GetGenericArguments () [1], SchemaContext); } if (!IsCollection) return null; if (!IsGeneric) return new XamlType (typeof (object), SchemaContext); return new XamlType (type.GetGenericArguments () [0], SchemaContext); } protected virtual XamlType LookupKeyType () { if (!IsDictionary) return null; if (!IsGeneric) return new XamlType (typeof (object), SchemaContext); return new XamlType (type.GetGenericArguments () [0], SchemaContext); } protected virtual XamlType LookupMarkupExtensionReturnType () { var a = this.GetCustomAttribute (); return a != null ? new XamlType (a.ReturnType, SchemaContext) : null; } protected virtual XamlMember LookupMember (string name, bool skipReadOnlyCheck) { // FIXME: verify if this does not filter out events. return GetAllMembers ().FirstOrDefault (m => m.Name == name && (skipReadOnlyCheck || !m.IsReadOnly || m.Type.IsCollection || m.Type.IsDictionary || m.Type.IsArray)); } protected virtual IList LookupPositionalParameters (int parameterCount) { if (UnderlyingType == null/* || !IsMarkupExtension*/) // see nunit tests... return null; // check if there is applicable ConstructorArgumentAttribute. // If there is, then return its type. if (parameterCount == 1) { foreach (var xm in GetAllMembers ()) { var ca = xm.GetCustomAttributeProvider ().GetCustomAttribute (false); if (ca != null) return new XamlType [] {xm.Type}; } } var methods = (from m in UnderlyingType.GetConstructors (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) where m.GetParameters ().Length == parameterCount select m).ToArray (); if (methods.Length == 1) return (from p in methods [0].GetParameters () select SchemaContext.GetXamlType (p.ParameterType)).ToArray (); if (SchemaContext.SupportMarkupExtensionsWithDuplicateArity) throw new NotSupportedException ("The default LookupPositionalParameters implementation does not allow duplicate arity of markup extensions"); return null; } BindingFlags flags_get_static = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; protected virtual EventHandler LookupSetMarkupExtensionHandler () { var a = this.GetCustomAttribute (); if (a == null) return null; var mi = type.GetMethod (a.XamlSetMarkupExtensionHandler, flags_get_static); if (mi == null) throw new ArgumentException ("Binding to XamlSetMarkupExtensionHandler failed"); return (EventHandler) Delegate.CreateDelegate (typeof (EventHandler), mi); } protected virtual EventHandler LookupSetTypeConverterHandler () { var a = this.GetCustomAttribute (); if (a == null) return null; var mi = type.GetMethod (a.XamlSetTypeConverterHandler, flags_get_static); if (mi == null) throw new ArgumentException ("Binding to XamlSetTypeConverterHandler failed"); return (EventHandler) Delegate.CreateDelegate (typeof (EventHandler), mi); } protected virtual bool LookupTrimSurroundingWhitespace () { return this.GetCustomAttribute () != null; } protected virtual XamlValueConverter LookupTypeConverter () { var t = UnderlyingType; if (t == null) return null; // equivalent to TypeExtension. // FIXME: not sure if it should be specially handled here. if (t == typeof (Type)) t = typeof (TypeExtension); var a = GetCustomAttributeProvider (); var ca = a != null ? a.GetCustomAttribute (false) : null; if (ca != null) return SchemaContext.GetValueConverter (Type.GetType (ca.ConverterTypeName), this); if (t == typeof (object)) // This is a special case. ConverterType is null. return SchemaContext.GetValueConverter (null, this); // It's still not decent to check CollectionConverter. var tct = t.GetTypeConverter ().GetType (); #if MOONLIGHT if (tct != typeof (TypeConverter) && tct.Name != "CollectionConverter" && tct.Name != "ReferenceConverter") #else if (tct != typeof (TypeConverter) && tct != typeof (CollectionConverter) && tct != typeof (ReferenceConverter)) #endif return SchemaContext.GetValueConverter (tct, this); return null; } protected virtual Type LookupUnderlyingType () { return underlying_type; } protected virtual bool LookupUsableDuringInitialization () { var a = this.GetCustomAttribute (); return a != null && a.Usable; } static XamlValueConverter string_value_serializer; protected virtual XamlValueConverter LookupValueSerializer () { return LookupValueSerializer (this, GetCustomAttributeProvider ()); } internal static XamlValueConverter LookupValueSerializer (XamlType targetType, ICustomAttributeProvider provider) { if (provider == null) return null; var a = provider.GetCustomAttribute (true); if (a != null) return new XamlValueConverter (a.ValueSerializerType ?? Type.GetType (a.ValueSerializerTypeName), targetType); if (targetType.BaseType != null) { var ret = targetType.BaseType.LookupValueSerializer (); if (ret != null) return ret; } if (targetType.UnderlyingType == typeof (string)) { if (string_value_serializer == null) string_value_serializer = new XamlValueConverter (typeof (StringValueSerializer), targetType); return string_value_serializer; } return null; } static string GetXamlName (Type type) { string n; if (!type.IsNestedPublic && !type.IsNestedAssembly && !type.IsNestedPrivate) n = type.Name; else n = GetXamlName (type.DeclaringType) + "+" + type.Name; if (type.IsGenericType && !type.ContainsGenericParameters) // the latter condition is to filter out "nested non-generic type within generic type". return n.Substring (0, n.IndexOf ('`')); else return n; } } }