2 // Copyright (C) 2010 Novell Inc. http://novell.com
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 using System.Collections;
25 using System.Collections.Generic;
26 using System.ComponentModel;
28 using System.Reflection;
29 using System.Windows.Markup;
30 using System.Xaml.Schema;
34 public class XamlType : IEquatable<XamlType>
36 public XamlType (Type underlyingType, XamlSchemaContext schemaContext)
37 : this (underlyingType, schemaContext, null)
41 // static readonly Type [] predefined_types = {
42 // typeof (XData), typeof (Uri), typeof (TimeSpan), typeof (PropertyDefinition), typeof (MemberDefinition), typeof (Reference)
45 public XamlType (Type underlyingType, XamlSchemaContext schemaContext, XamlTypeInvoker invoker)
46 : this (schemaContext, invoker)
48 if (underlyingType == null)
49 throw new ArgumentNullException ("underlyingType");
50 type = underlyingType;
51 underlying_type = type;
54 if (XamlLanguage.InitializingTypes) {
55 Name = GetXamlName (type);
56 PreferredXamlNamespace = XamlLanguage.Xaml2006Namespace;
57 } else if ((xt = XamlLanguage.AllTypes.FirstOrDefault (t => t.UnderlyingType == type)) != null) {
59 PreferredXamlNamespace = XamlLanguage.Xaml2006Namespace;
61 Name = GetXamlName (type);
62 PreferredXamlNamespace = String.Format ("clr-namespace:{0};assembly={1}", type.Namespace, type.Assembly.GetName ().Name);
64 if (type.IsGenericType) {
65 TypeArguments = new List<XamlType> ();
66 foreach (var gta in type.GetGenericArguments ())
67 TypeArguments.Add (schemaContext.GetXamlType (gta));
71 public XamlType (string unknownTypeNamespace, string unknownTypeName, IList<XamlType> typeArguments, XamlSchemaContext schemaContext)
72 : this (schemaContext, null)
74 if (unknownTypeNamespace == null)
75 throw new ArgumentNullException ("unknownTypeNamespace");
76 if (unknownTypeName == null)
77 throw new ArgumentNullException ("unknownTypeName");
78 if (schemaContext == null)
79 throw new ArgumentNullException ("schemaContext");
81 type = typeof (object);
82 Name = unknownTypeName;
83 PreferredXamlNamespace = unknownTypeNamespace;
84 TypeArguments = typeArguments != null && typeArguments.Count == 0 ? null : typeArguments;
85 explicit_ns = unknownTypeNamespace;
88 protected XamlType (string typeName, IList<XamlType> typeArguments, XamlSchemaContext schemaContext)
89 : this (String.Empty, typeName, typeArguments, schemaContext)
93 XamlType (XamlSchemaContext schemaContext, XamlTypeInvoker invoker)
95 if (schemaContext == null)
96 throw new ArgumentNullException ("schemaContext");
97 SchemaContext = schemaContext;
98 this.invoker = invoker ?? new XamlTypeInvoker (this);
101 Type type, underlying_type;
105 // populated properties
107 XamlTypeInvoker invoker;
109 internal EventHandler<XamlSetMarkupExtensionEventArgs> SetMarkupExtensionHandler {
110 get { return LookupSetMarkupExtensionHandler (); }
113 internal EventHandler<XamlSetTypeConverterEventArgs> SetTypeConverterHandler {
114 get { return LookupSetTypeConverterHandler (); }
117 public IList<XamlType> AllowedContentTypes {
118 get { return LookupAllowedContentTypes (); }
121 public XamlType BaseType {
122 get { return LookupBaseType (); }
125 public bool ConstructionRequiresArguments {
126 get { return LookupConstructionRequiresArguments (); }
129 public XamlMember ContentProperty {
130 get { return LookupContentProperty (); }
133 public IList<XamlType> ContentWrappers {
134 get { return LookupContentWrappers (); }
137 public XamlValueConverter<XamlDeferringLoader> DeferringLoader {
138 get { return LookupDeferringLoader (); }
141 public XamlTypeInvoker Invoker {
142 get { return LookupInvoker (); }
145 public bool IsAmbient {
146 get { return LookupIsAmbient (); }
149 public bool IsArray {
150 get { return LookupCollectionKind () == XamlCollectionKind.Array; }
153 // it somehow treats array as not a collection...
154 public bool IsCollection {
155 get { return LookupCollectionKind () == XamlCollectionKind.Collection; }
158 public bool IsConstructible {
159 get { return LookupIsConstructible (); }
162 public bool IsDictionary {
163 get { return LookupCollectionKind () == XamlCollectionKind.Dictionary; }
166 public bool IsGeneric {
167 get { return type.IsGenericType; }
170 public bool IsMarkupExtension {
171 get { return LookupIsMarkupExtension (); }
173 public bool IsNameScope {
174 get { return LookupIsNameScope (); }
176 public bool IsNameValid {
177 get { return XamlLanguage.IsValidXamlName (Name); }
180 public bool IsNullable {
181 get { return LookupIsNullable (); }
184 public bool IsPublic {
185 get { return LookupIsPublic (); }
188 public bool IsUnknown {
189 get { return LookupIsUnknown (); }
192 public bool IsUsableDuringInitialization {
193 get { return LookupUsableDuringInitialization (); }
196 public bool IsWhitespaceSignificantCollection {
197 get { return LookupIsWhitespaceSignificantCollection (); }
200 public bool IsXData {
201 get { return LookupIsXData (); }
204 public XamlType ItemType {
205 get { return LookupItemType (); }
208 public XamlType KeyType {
209 get { return LookupKeyType (); }
212 public XamlType MarkupExtensionReturnType {
213 get { return LookupMarkupExtensionReturnType (); }
216 public string Name { get; private set; }
218 public string PreferredXamlNamespace { get; private set; }
220 public XamlSchemaContext SchemaContext { get; private set; }
222 public bool TrimSurroundingWhitespace {
223 get { return LookupTrimSurroundingWhitespace (); }
226 public IList<XamlType> TypeArguments { get; private set; }
228 public XamlValueConverter<TypeConverter> TypeConverter {
229 get { return LookupTypeConverter (); }
232 public Type UnderlyingType {
233 get { return LookupUnderlyingType (); }
236 public XamlValueConverter<ValueSerializer> ValueSerializer {
237 get { return LookupValueSerializer (); }
240 internal string GetInternalXmlName ()
242 if (IsMarkupExtension && Name.EndsWith ("Extension", StringComparison.Ordinal))
243 return Name.Substring (0, Name.Length - 9);
244 var stn = XamlLanguage.SpecialNames.FirstOrDefault (s => s.Type == this);
245 return stn != null ? stn.Name : Name;
248 public static bool operator == (XamlType left, XamlType right)
250 return IsNull (left) ? IsNull (right) : left.Equals (right);
253 static bool IsNull (XamlType a)
255 return Object.ReferenceEquals (a, null);
258 public static bool operator != (XamlType left, XamlType right)
260 return !(left == right);
263 public bool Equals (XamlType other)
265 return !IsNull (other) &&
266 UnderlyingType == other.UnderlyingType &&
267 Name == other.Name &&
268 PreferredXamlNamespace == other.PreferredXamlNamespace && TypeArguments.ListEquals (other.TypeArguments);
271 public override bool Equals (object obj)
273 var a = obj as XamlType;
277 public override int GetHashCode ()
279 if (UnderlyingType != null)
280 return UnderlyingType.GetHashCode ();
281 int x = Name.GetHashCode () << 7 + PreferredXamlNamespace.GetHashCode ();
282 if (TypeArguments != null)
283 foreach (var t in TypeArguments)
284 x = t.GetHashCode () + x << 5;
288 public override string ToString ()
290 return new XamlTypeName (this).ToString ();
291 //return String.IsNullOrEmpty (PreferredXamlNamespace) ? Name : String.Concat ("{", PreferredXamlNamespace, "}", Name);
294 public virtual bool CanAssignTo (XamlType xamlType)
296 throw new NotImplementedException ();
299 public XamlMember GetAliasedProperty (XamlDirective directive)
301 return LookupAliasedProperty (directive);
304 public ICollection<XamlMember> GetAllAttachableMembers ()
306 return new List<XamlMember> (LookupAllAttachableMembers ());
309 public ICollection<XamlMember> GetAllMembers ()
311 return new List<XamlMember> (LookupAllMembers ());
314 public XamlMember GetAttachableMember (string name)
316 return LookupAttachableMember (name);
319 public XamlMember GetMember (string name)
321 return LookupMember (name, true);
324 public IList<XamlType> GetPositionalParameters (int parameterCount)
326 return LookupPositionalParameters (parameterCount);
329 public virtual IList<string> GetXamlNamespaces ()
331 throw new NotImplementedException ();
332 /* this does not work like documented!
333 if (explicit_ns != null)
334 return new string [] {explicit_ns};
335 var l = SchemaContext.GetAllXamlNamespaces ();
337 return new List<string> (l);
338 return new string [] {String.Empty};
344 protected virtual XamlMember LookupAliasedProperty (XamlDirective directive)
346 if (directive == XamlLanguage.Key) {
347 var a = this.GetCustomAttribute<DictionaryKeyPropertyAttribute> ();
348 return a != null ? GetMember (a.Name) : null;
350 if (directive == XamlLanguage.Name) {
351 var a = this.GetCustomAttribute<RuntimeNamePropertyAttribute> ();
352 return a != null ? GetMember (a.Name) : null;
354 if (directive == XamlLanguage.Uid) {
355 var a = this.GetCustomAttribute<UidPropertyAttribute> ();
356 return a != null ? GetMember (a.Name) : null;
358 if (directive == XamlLanguage.Lang) {
359 var a = this.GetCustomAttribute<XmlLangPropertyAttribute> ();
360 return a != null ? GetMember (a.Name) : null;
365 protected virtual IEnumerable<XamlMember> LookupAllAttachableMembers ()
367 if (UnderlyingType == null)
368 return BaseType != null ? BaseType.GetAllAttachableMembers () : null;
369 return DoLookupAllAttachableMembers ();
372 IEnumerable<XamlMember> DoLookupAllAttachableMembers ()
374 yield break; // FIXME: what to return here?
377 static readonly XamlMember [] empty_array = new XamlMember [0];
379 protected virtual IEnumerable<XamlMember> LookupAllMembers ()
381 if (UnderlyingType == null)
382 return BaseType != null ? BaseType.GetAllMembers () : empty_array;
383 if (all_members_cache == null) {
384 all_members_cache = new List<XamlMember> (DoLookupAllMembers ());
385 all_members_cache.Sort (CompareMembers);
387 return all_members_cache;
390 int CompareMembers (XamlMember m1, XamlMember m2)
392 // ConstructorArguments and PositionalParameters go first.
393 if (m1 == XamlLanguage.PositionalParameters)
395 if (m2 == XamlLanguage.PositionalParameters)
397 if (m1.IsConstructorArgument) {
398 if (!m2.IsConstructorArgument)
401 else if (m2.IsConstructorArgument)
404 // ContentProperty is returned at last.
405 if (m1.DeclaringType.ContentProperty == m1)
407 if (m2.DeclaringType.ContentProperty == m2)
410 // compare collection kind
413 int coll1 = t1.IsDictionary ? 3 : t1.IsCollection ? 2 : t1.IsArray ? 1 : 0;
414 int coll2 = t2.IsDictionary ? 3 : t2.IsCollection ? 2 : t2.IsArray ? 1 : 0;
416 return coll2 - coll1;
418 // then, compare names.
419 return String.CompareOrdinal (m1.Name, m2.Name);
422 List<XamlMember> all_members_cache;
424 IEnumerable<XamlMember> DoLookupAllMembers ()
426 // This is a hack that is likely required due to internal implementation difference in System.Uri. Our Uri has two readonly collection properties
427 if (this == XamlLanguage.Uri)
430 var bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
432 foreach (var pi in UnderlyingType.GetProperties (bf))
433 if (pi.CanRead && (pi.CanWrite && pi.GetIndexParameters ().Length == 0 || IsCollectionType (pi.PropertyType)))
434 yield return new XamlMember (pi, SchemaContext);
435 foreach (var ei in UnderlyingType.GetEvents (bf))
436 yield return new XamlMember (ei, SchemaContext);
439 static bool IsPublicAccessor (MethodInfo mi)
441 return mi != null && mi.IsPublic;
444 bool IsCollectionType (Type type)
448 var xt = SchemaContext.GetXamlType (type);
449 return xt.LookupCollectionKind () != XamlCollectionKind.None;
452 protected virtual IList<XamlType> LookupAllowedContentTypes ()
454 // the actual implementation is very different from what is documented :(
458 var l = new List<XamlType> ();
459 if (ContentWrappers != null)
460 l.AddRange (ContentWrappers);
461 if (ContentProperty != null)
462 l.Add (ContentProperty.Type);
463 if (ItemType != null)
465 return l.Count > 0 ? l : null;
469 protected virtual XamlMember LookupAttachableMember (string name)
471 throw new NotImplementedException ();
475 protected virtual XamlType LookupBaseType ()
477 if (base_type == null) {
478 if (UnderlyingType == null)
479 // FIXME: probably something advanced is needed here.
480 base_type = new XamlType (typeof (object), SchemaContext, Invoker);
482 base_type = type.BaseType == null || type.BaseType == typeof (object) ? null : new XamlType (type.BaseType, SchemaContext, Invoker);
487 // This implementation is not verified. (No place to use.)
488 protected virtual XamlCollectionKind LookupCollectionKind ()
490 if (UnderlyingType == null)
491 return BaseType != null ? BaseType.LookupCollectionKind () : XamlCollectionKind.None;
493 return XamlCollectionKind.Array;
495 if (type.ImplementsAnyInterfacesOf (typeof (IDictionary), typeof (IDictionary<,>)))
496 return XamlCollectionKind.Dictionary;
498 if (type.ImplementsAnyInterfacesOf (typeof (IList), typeof (ICollection<>)))
499 return XamlCollectionKind.Collection;
501 return XamlCollectionKind.None;
504 protected virtual bool LookupConstructionRequiresArguments ()
506 if (UnderlyingType == null)
509 // not sure if it is required, but MemberDefinition return true while they are abstract and it makes no sense.
510 if (UnderlyingType.IsAbstract)
513 // FIXME: probably some primitive types are treated as special.
514 switch (Type.GetTypeCode (UnderlyingType)) {
515 case TypeCode.String:
517 case TypeCode.Object:
518 if (UnderlyingType == typeof (TimeSpan))
525 return UnderlyingType.GetConstructor (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null) == null;
528 protected virtual XamlMember LookupContentProperty ()
530 var a = this.GetCustomAttribute<ContentPropertyAttribute> ();
531 return a != null && a.Name != null ? GetMember (a.Name) : null;
534 protected virtual IList<XamlType> LookupContentWrappers ()
536 if (GetCustomAttributeProvider () == null)
539 var arr = GetCustomAttributeProvider ().GetCustomAttributes (typeof (ContentWrapperAttribute), false);
540 if (arr == null || arr.Length == 0)
542 var l = new XamlType [arr.Length];
543 for (int i = 0; i < l.Length; i++)
544 l [i] = SchemaContext.GetXamlType (((ContentWrapperAttribute) arr [i]).ContentWrapper);
548 internal ICustomAttributeProvider GetCustomAttributeProvider ()
550 return LookupCustomAttributeProvider ();
553 protected internal virtual ICustomAttributeProvider LookupCustomAttributeProvider ()
555 return UnderlyingType;
558 protected virtual XamlValueConverter<XamlDeferringLoader> LookupDeferringLoader ()
560 throw new NotImplementedException ();
563 protected virtual XamlTypeInvoker LookupInvoker ()
568 protected virtual bool LookupIsAmbient ()
570 return this.GetCustomAttribute<AmbientAttribute> () != null;
573 // It is documented as if it were to reflect spec. section 5.2,
574 // but the actual behavior shows it is *totally* wrong.
575 // Here I have implemented this based on the nunit test results. sigh.
576 protected virtual bool LookupIsConstructible ()
578 if (UnderlyingType == null)
580 if (IsMarkupExtension)
582 if (UnderlyingType.IsAbstract)
589 protected virtual bool LookupIsMarkupExtension ()
591 return typeof (MarkupExtension).IsAssignableFrom (UnderlyingType);
594 protected virtual bool LookupIsNameScope ()
596 return typeof (INameScope).IsAssignableFrom (UnderlyingType);
599 protected virtual bool LookupIsNullable ()
601 return !type.IsValueType || type.ImplementsInterface (typeof (Nullable<>));
604 protected virtual bool LookupIsPublic ()
606 return underlying_type == null || underlying_type.IsPublic || underlying_type.IsNestedPublic;
609 protected virtual bool LookupIsUnknown ()
611 return UnderlyingType == null;
614 protected virtual bool LookupIsWhitespaceSignificantCollection ()
616 // probably for unknown types, it should preserve whitespaces.
617 return IsUnknown || this.GetCustomAttribute<WhitespaceSignificantCollectionAttribute> () != null;
620 protected virtual bool LookupIsXData ()
622 // huh? XamlLanguage.XData.IsXData returns false(!)
623 // return typeof (XData).IsAssignableFrom (UnderlyingType);
627 protected virtual XamlType LookupItemType ()
630 return new XamlType (type.GetElementType (), SchemaContext);
633 return new XamlType (typeof (object), SchemaContext);
634 return new XamlType (type.GetGenericArguments () [1], SchemaContext);
639 return new XamlType (typeof (object), SchemaContext);
640 return new XamlType (type.GetGenericArguments () [0], SchemaContext);
643 protected virtual XamlType LookupKeyType ()
648 return new XamlType (typeof (object), SchemaContext);
649 return new XamlType (type.GetGenericArguments () [0], SchemaContext);
652 protected virtual XamlType LookupMarkupExtensionReturnType ()
654 var a = this.GetCustomAttribute<MarkupExtensionReturnTypeAttribute> ();
655 return a != null ? new XamlType (a.ReturnType, SchemaContext) : null;
658 protected virtual XamlMember LookupMember (string name, bool skipReadOnlyCheck)
660 // FIXME: verify if this does not filter out events.
661 return GetAllMembers ().FirstOrDefault (m => m.Name == name && (skipReadOnlyCheck || !m.IsReadOnly || m.Type.IsCollection || m.Type.IsDictionary || m.Type.IsArray));
664 protected virtual IList<XamlType> LookupPositionalParameters (int parameterCount)
666 if (UnderlyingType == null/* || !IsMarkupExtension*/) // see nunit tests...
669 // check if there is applicable ConstructorArgumentAttribute.
670 // If there is, then return its type.
671 if (parameterCount == 1) {
672 foreach (var xm in GetAllMembers ()) {
673 var ca = xm.GetCustomAttributeProvider ().GetCustomAttribute<ConstructorArgumentAttribute> (false);
675 return new XamlType [] {xm.Type};
679 var methods = (from m in UnderlyingType.GetConstructors (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) where m.GetParameters ().Length == parameterCount select m).ToArray ();
680 if (methods.Length == 1)
681 return (from p in methods [0].GetParameters () select SchemaContext.GetXamlType (p.ParameterType)).ToArray ();
683 if (SchemaContext.SupportMarkupExtensionsWithDuplicateArity)
684 throw new NotSupportedException ("The default LookupPositionalParameters implementation does not allow duplicate arity of markup extensions");
688 BindingFlags flags_get_static = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
690 protected virtual EventHandler<XamlSetMarkupExtensionEventArgs> LookupSetMarkupExtensionHandler ()
692 var a = this.GetCustomAttribute<XamlSetMarkupExtensionAttribute> ();
695 var mi = type.GetMethod (a.XamlSetMarkupExtensionHandler, flags_get_static);
697 throw new ArgumentException ("Binding to XamlSetMarkupExtensionHandler failed");
698 return (EventHandler<XamlSetMarkupExtensionEventArgs>) Delegate.CreateDelegate (typeof (EventHandler<XamlSetMarkupExtensionEventArgs>), mi);
701 protected virtual EventHandler<XamlSetTypeConverterEventArgs> LookupSetTypeConverterHandler ()
703 var a = this.GetCustomAttribute<XamlSetTypeConverterAttribute> ();
706 var mi = type.GetMethod (a.XamlSetTypeConverterHandler, flags_get_static);
708 throw new ArgumentException ("Binding to XamlSetTypeConverterHandler failed");
709 return (EventHandler<XamlSetTypeConverterEventArgs>) Delegate.CreateDelegate (typeof (EventHandler<XamlSetTypeConverterEventArgs>), mi);
712 protected virtual bool LookupTrimSurroundingWhitespace ()
714 return this.GetCustomAttribute<TrimSurroundingWhitespaceAttribute> () != null;
717 protected virtual XamlValueConverter<TypeConverter> LookupTypeConverter ()
719 var t = UnderlyingType;
723 // equivalent to TypeExtension.
724 // FIXME: not sure if it should be specially handled here.
725 if (t == typeof (Type))
726 t = typeof (TypeExtension);
728 var a = GetCustomAttributeProvider ().GetCustomAttribute<TypeConverterAttribute> (false);
730 return SchemaContext.GetValueConverter<TypeConverter> (Type.GetType (a.ConverterTypeName), this);
732 if (t == typeof (object))
733 return SchemaContext.GetValueConverter<TypeConverter> (typeof (TypeConverter), this);
735 // It's still not decent to check CollectionConverter.
736 var tct = TypeDescriptor.GetConverter (t).GetType ();
737 if (tct != typeof (TypeConverter) && tct != typeof (CollectionConverter) && tct != typeof (ReferenceConverter))
738 return SchemaContext.GetValueConverter<TypeConverter> (tct, this);
742 protected virtual Type LookupUnderlyingType ()
744 return underlying_type;
747 protected virtual bool LookupUsableDuringInitialization ()
749 var a = this.GetCustomAttribute<UsableDuringInitializationAttribute> ();
750 return a != null && a.Usable;
753 static XamlValueConverter<ValueSerializer> string_value_serializer;
755 protected virtual XamlValueConverter<ValueSerializer> LookupValueSerializer ()
757 return LookupValueSerializer (this, GetCustomAttributeProvider ());
760 internal static XamlValueConverter<ValueSerializer> LookupValueSerializer (XamlType targetType, ICustomAttributeProvider provider)
762 if (provider == null)
765 var a = provider.GetCustomAttribute<ValueSerializerAttribute> (true);
767 return new XamlValueConverter<ValueSerializer> (a.ValueSerializerType ?? Type.GetType (a.ValueSerializerTypeName), targetType);
769 if (targetType.BaseType != null) {
770 var ret = targetType.BaseType.LookupValueSerializer ();
775 if (targetType.UnderlyingType == typeof (string)) {
776 if (string_value_serializer == null)
777 string_value_serializer = new XamlValueConverter<ValueSerializer> (typeof (StringValueSerializer), targetType);
778 return string_value_serializer;
784 static string GetXamlName (Type type)
790 n = GetXamlName (type.DeclaringType) + "+" + type.Name;
791 if (type.IsGenericType && !type.ContainsGenericParameters) // the latter condition is to filter out "nested non-generic type within generic type".
792 return n.Substring (0, n.IndexOf ('`'));