Release 10.0.0.16997
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / Xaml / ApplyPropertiesVisitor.cs
1 /*
2  * Copyright(c) 2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 using System;
19 using System.Collections;
20 using System.Collections.Generic;
21 using System.Linq;
22 using System.Reflection;
23 using System.Xml;
24 using Tizen.NUI.Binding.Internals;
25 using Tizen.NUI.Binding;
26
27 using static System.String;
28
29 namespace Tizen.NUI.Xaml
30 {
31     internal class ApplyPropertiesVisitor : IXamlNodeVisitor
32     {
33         public static readonly IList<XmlName> Skips = new List<XmlName> {
34             XmlName.xKey,
35             XmlName.xTypeArguments,
36             XmlName.xArguments,
37             XmlName.xFactoryMethod,
38             XmlName.xName,
39             XmlName.xDataType
40         };
41
42         public ApplyPropertiesVisitor(HydrationContext context, bool stopOnResourceDictionary = false)
43         {
44             Context = context;
45             StopOnResourceDictionary = stopOnResourceDictionary;
46         }
47
48         Dictionary<INode, object> Values => Context.Values;
49         HydrationContext Context { get; }
50
51         public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp;
52         public bool StopOnDataTemplate => true;
53         public bool StopOnResourceDictionary { get; }
54         public bool VisitNodeOnDataTemplate => true;
55         public bool SkipChildren(INode node, INode parentNode) => false;
56         public bool IsResourceDictionary(ElementNode node) => typeof(ResourceDictionary).IsAssignableFrom(Context.Types[node]);
57
58         public void Visit(ValueNode node, INode parentNode)
59         {
60             var parentElement = parentNode as IElementNode;
61             var value = Values[node];
62             var source = Values[parentNode];
63             XmlName propertyName;
64
65             if (TryGetPropertyName(node, parentNode, out propertyName))
66             {
67                 if (TrySetRuntimeName(propertyName, source, value, node))
68                     return;
69                 if (Skips.Contains(propertyName))
70                     return;
71                 if (parentElement.SkipProperties.Contains(propertyName))
72                     return;
73                 if (propertyName.Equals(XamlParser.McUri, "Ignorable"))
74                     return;
75                 SetPropertyValue(source, propertyName, value, Context.RootElement, node, Context, node);
76             }
77             else if (IsCollectionItem(node, parentNode) && parentNode is IElementNode)
78             {
79                 // Collection element, implicit content, or implicit collection element.
80                 var contentProperty = GetContentPropertyName(Context.Types[parentElement].GetTypeInfo());
81                 if (contentProperty != null)
82                 {
83                     var name = new XmlName(((ElementNode)parentNode).NamespaceURI, contentProperty);
84                     if (Skips.Contains(name))
85                         return;
86                     if (parentElement.SkipProperties.Contains(propertyName))
87                         return;
88                     SetPropertyValue(source, name, value, Context.RootElement, node, Context, node);
89                 }
90             }
91         }
92
93         public void Visit(MarkupNode node, INode parentNode)
94         {
95         }
96
97         public void Visit(ElementNode node, INode parentNode)
98         {
99             XmlName propertyName;
100             if (TryGetPropertyName(node, parentNode, out propertyName) && propertyName == XmlName._CreateContent)
101             {
102                 var s0 = Values[parentNode];
103                 if (s0 is ElementTemplate)
104                 {
105                     SetTemplate(s0 as ElementTemplate, node);
106                     return;
107                 }
108             }
109
110             var parentElement = parentNode as IElementNode;
111             propertyName = XmlName.Empty;
112
113             //Simplify ListNodes with single elements
114             var pList = parentNode as ListNode;
115             if (pList != null && pList.CollectionItems.Count == 1)
116             {
117                 propertyName = pList.XmlName;
118                 parentNode = parentNode.Parent;
119                 parentElement = parentNode as IElementNode;
120             }
121
122             var value = Values[node];
123
124             if (propertyName != XmlName.Empty || TryGetPropertyName(node, parentNode, out propertyName))
125             {
126                 if (Skips.Contains(propertyName))
127                     return;
128                 if (parentElement == null)
129                     return;
130                 if (parentElement.SkipProperties.Contains(propertyName))
131                     return;
132
133                 var source = Values[parentNode];
134                 ProvideValue(ref value, node, source, propertyName);
135                 SetPropertyValue(source, propertyName, value, Context.RootElement, node, Context, node);
136             }
137             else if (IsCollectionItem(node, parentNode) && parentNode is IElementNode)
138             {
139                 var source = Values[parentNode];
140                 ProvideValue(ref value, node, source, XmlName.Empty);
141                 string contentProperty;
142                 Exception xpe = null;
143                 var xKey = node.Properties.ContainsKey(XmlName.xKey) ? ((ValueNode)node.Properties[XmlName.xKey]).Value as string : null;
144
145                 //ResourceDictionary
146                 if (xpe == null && TryAddToResourceDictionary(source as ResourceDictionary, value, xKey, node, out xpe))
147                     return;
148
149                 // Dictionary with string key
150                 if (xpe == null && xKey != null)
151                 {
152                     var indexer = GetIndexer(source, typeof(string), value.GetType());
153                     if (indexer != null)
154                     {
155                         indexer.SetValue(source, value, new[] { xKey });
156                         return;
157                     }
158                 }
159
160                 // Collection element, implicit content, or implicit collection element.
161                 if (xpe == null && typeof(IEnumerable).IsAssignableFrom(Context.Types[parentElement]) && Context.Types[parentElement].GetRuntimeMethods().Any(mi => mi.Name == "Add" && mi.GetParameters().Length == 1))
162                 {
163                     var addMethod =
164                         Context.Types[parentElement].GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1);
165
166                     addMethod?.Invoke(source, new[] { value });
167                     return;
168                 }
169                 if (xpe == null && Context.Types[parentElement].GetRuntimeMethods().Any
170                     (mi => mi.Name == "Add" && mi.GetParameters().Length == 1 && mi.GetParameters()[0].ParameterType.IsAssignableFrom(value.GetType())))
171                 {
172                     //if there are similar parameters in the function, this will exist issue.
173                     var addMethod = Context.Types[parentElement].GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1);
174                     if (addMethod != null) addMethod.Invoke(source, new[] { value });
175                     return;
176                 }
177                 if (xpe == null && (contentProperty = GetContentPropertyName(Context.Types[parentElement].GetTypeInfo())) != null)
178                 {
179                     var name = new XmlName(node.NamespaceURI, contentProperty);
180                     if (Skips.Contains(name))
181                         return;
182                     if (parentElement.SkipProperties.Contains(propertyName))
183                         return;
184
185                     SetPropertyValue(source, name, value, Context.RootElement, node, Context, node);
186                     return;
187                 }
188
189                 xpe = xpe ?? new XamlParseException($"Can not set the content of {((IElementNode)parentNode).XmlType.Name} as it doesn't have a ContentPropertyAttribute", node);
190                 if (Context.ExceptionHandler != null)
191                     Context.ExceptionHandler(xpe);
192                 throw xpe;
193             }
194             else if (IsCollectionItem(node, parentNode) && parentNode is ListNode)
195             {
196                 var source = Values[parentNode.Parent];
197                 ProvideValue(ref value, node, source, XmlName.Empty);
198                 var parentList = (ListNode)parentNode;
199                 if (Skips.Contains(parentList.XmlName))
200                     return;
201                 Exception xpe = null;
202                 var xKey = node.Properties.ContainsKey(XmlName.xKey) ? ((ValueNode)node.Properties[XmlName.xKey]).Value as string : null;
203
204                 object _;
205                 var collection = GetPropertyValue(source, parentList.XmlName, Context, parentList, out _) as IEnumerable;
206                 if (collection == null)
207                     xpe = new XamlParseException($"Property {parentList.XmlName.LocalName} is null or is not IEnumerable", node);
208
209                 if (xpe == null && TryAddToResourceDictionary(collection as ResourceDictionary, value, xKey, node, out xpe))
210                     return;
211
212                 MethodInfo addMethod;
213                 if (xpe == null && (addMethod = collection.GetType().GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1)) != null)
214                 {
215                     addMethod.Invoke(collection, new[] { value });
216                     return;
217                 }
218                 xpe = xpe ?? new XamlParseException($"Value of {parentList.XmlName.LocalName} does not have a Add() method", node);
219                 if (Context.ExceptionHandler != null)
220                     Context.ExceptionHandler(xpe);
221                 else
222                     throw xpe;
223             }
224         }
225
226
227
228         public void Visit(RootNode node, INode parentNode)
229         {
230         }
231
232         public void Visit(ListNode node, INode parentNode)
233         {
234         }
235
236         public static bool TryGetPropertyName(INode node, INode parentNode, out XmlName name)
237         {
238             name = default(XmlName);
239             var parentElement = parentNode as IElementNode;
240             if (parentElement == null)
241                 return false;
242             foreach (var kvp in parentElement.Properties)
243             {
244                 if (kvp.Value != node)
245                     continue;
246                 name = kvp.Key;
247                 return true;
248             }
249             return false;
250         }
251
252         internal static bool IsCollectionItem(INode node, INode parentNode)
253         {
254             var parentList = parentNode as IListNode;
255             if (parentList == null)
256                 return false;
257             return parentList.CollectionItems.Contains(node);
258         }
259
260         internal static string GetContentPropertyName(System.Reflection.TypeInfo typeInfo)
261         {
262             while (typeInfo != null)
263             {
264                 var propName = GetContentPropertyName(typeInfo.CustomAttributes);
265                 if (propName != null)
266                     return propName;
267                 typeInfo = typeInfo?.BaseType?.GetTypeInfo();
268             }
269             return null;
270         }
271
272         void ProvideValue(ref object value, ElementNode node, object source, XmlName propertyName)
273         {
274             var markupExtension = value as IMarkupExtension;
275             var valueProvider = value as IValueProvider;
276
277             if (markupExtension == null && valueProvider == null)
278                 return;
279
280             XamlServiceProvider serviceProvider = null;
281             if (value.GetType().GetTypeInfo().GetCustomAttribute<AcceptEmptyServiceProviderAttribute>() == null)
282                 serviceProvider = new XamlServiceProvider(node, Context);
283
284             if (serviceProvider != null && serviceProvider.IProvideValueTarget is XamlValueTargetProvider && propertyName != XmlName.Empty)
285             {
286                 (serviceProvider.IProvideValueTarget as XamlValueTargetProvider).TargetProperty = GetTargetProperty(source, propertyName, Context, node);
287             }
288
289             if (markupExtension != null)
290                 value = markupExtension.ProvideValue(serviceProvider);
291             else if (valueProvider != null)
292                 value = valueProvider.ProvideValue(serviceProvider);
293         }
294
295         static string GetContentPropertyName(IEnumerable<CustomAttributeData> attributes)
296         {
297             var contentAttribute =
298                 attributes.FirstOrDefault(cad => ContentPropertyAttribute.ContentPropertyTypes.Contains(cad.AttributeType.FullName));
299             if (contentAttribute == null || contentAttribute.ConstructorArguments.Count != 1)
300                 return null;
301             if (contentAttribute.ConstructorArguments[0].ArgumentType == typeof(string))
302                 return (string)contentAttribute.ConstructorArguments[0].Value;
303             return null;
304         }
305
306         static bool GetRealNameAndType(ref Type elementType, string namespaceURI, ref string localname,
307             HydrationContext context, IXmlLineInfo lineInfo)
308         {
309             var dotIdx = localname.IndexOf('.');
310             if (dotIdx > 0)
311             {
312                 var typename = localname.Substring(0, dotIdx);
313                 localname = localname.Substring(dotIdx + 1);
314                 XamlParseException xpe;
315                 elementType = XamlParser.GetElementType(new XmlType(namespaceURI, typename, null), lineInfo,
316                     context.RootElement.GetType().GetTypeInfo().Assembly, out xpe);
317
318                 if (xpe != null)
319                     throw xpe;
320                 return true;
321             }
322             return false;
323         }
324
325         static BindableProperty GetBindableProperty(Type elementType, string localName, IXmlLineInfo lineInfo,
326             bool throwOnError = false)
327         {
328 #if NETSTANDARD1_0
329             var bindableFieldInfo = elementType.GetFields().FirstOrDefault(fi => fi.Name == localName + "Property");
330 #else
331             var bindableFieldInfo = elementType.GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy).FirstOrDefault(fi => fi.Name == localName + "Property");
332
333             if (null == bindableFieldInfo)
334             {
335                 bindableFieldInfo = elementType.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy).FirstOrDefault(fi => fi.Name == localName + "Property");
336             }
337 #endif
338             Exception exception = null;
339             if (exception == null && bindableFieldInfo == null)
340             {
341                 exception =
342                     new XamlParseException(
343                         Format("BindableProperty {0} not found on {1}", localName + "Property", elementType.Name), lineInfo);
344             }
345
346             if (exception == null)
347                 return bindableFieldInfo.GetValue(null) as BindableProperty;
348             if (throwOnError)
349                 throw exception;
350             return null;
351         }
352
353         static object GetTargetProperty(object xamlelement, XmlName propertyName, HydrationContext context, IXmlLineInfo lineInfo)
354         {
355             var localName = propertyName.LocalName;
356             //If it's an attached BP, update elementType and propertyName
357             var bpOwnerType = xamlelement.GetType();
358             GetRealNameAndType(ref bpOwnerType, propertyName.NamespaceURI, ref localName, context, lineInfo);
359             var property = GetBindableProperty(bpOwnerType, localName, lineInfo, false);
360
361             if (property != null)
362                 return property;
363
364             var elementType = xamlelement.GetType();
365             var propertyInfo = elementType.GetRuntimeProperties().FirstOrDefault(p => p.Name == localName);
366             return propertyInfo;
367         }
368
369         public static void SetPropertyValue(object xamlelement, XmlName propertyName, object value, object rootElement, INode node, HydrationContext context, IXmlLineInfo lineInfo)
370         {
371             var localName = propertyName.LocalName;
372             var serviceProvider = new XamlServiceProvider(node, context);
373             Exception xpe = null;
374             var xKey = node is IElementNode && ((IElementNode)node).Properties.ContainsKey(XmlName.xKey) ? ((ValueNode)((IElementNode)node).Properties[XmlName.xKey]).Value as string : null;
375
376             //If it's an attached BP, update elementType and propertyName
377             var bpOwnerType = xamlelement.GetType();
378             var attached = GetRealNameAndType(ref bpOwnerType, propertyName.NamespaceURI, ref localName, context, lineInfo);
379
380             var property = GetBindableProperty(bpOwnerType, localName, lineInfo, false);
381
382             //If the target is an event, connect
383             if (xpe == null && TryConnectEvent(xamlelement, localName, attached, value, rootElement, lineInfo, out xpe))
384                 return;
385
386             //If Value is DynamicResource and it's a BP, SetDynamicResource
387             if (xpe == null && TrySetDynamicResource(xamlelement, property, value, lineInfo, out xpe))
388                 return;
389
390             //If value is BindingBase, SetBinding
391             if (xpe == null && TrySetBinding(xamlelement, property, value, lineInfo, out xpe))
392                 return;
393
394             //Call TrySetProperty first and then TrySetValue to keep the code logic consistent whether it is through xaml or code.
395             //If we can assign that value to a normal property, let's do it
396             if (xpe == null && TrySetProperty(xamlelement, property, localName, value, serviceProvider, context, out xpe))
397                 return;
398
399             //If it's a BindableProberty, SetValue
400             if (xpe == null && TrySetValue(xamlelement, property, attached, value, lineInfo, serviceProvider, out xpe))
401                 return;
402
403             //If it's an already initialized property, add to it
404             if (xpe == null && TryAddToProperty(xamlelement, propertyName, value, xKey, lineInfo, serviceProvider, context, out xpe))
405                 return;
406
407             xpe = xpe ?? new XamlParseException($"Cannot assign property \"{localName}\": Property does not exist, or is not assignable, or mismatching type between value and property", lineInfo);
408             if (context.ExceptionHandler != null)
409                 context.ExceptionHandler(xpe);
410             else
411                 throw xpe;
412         }
413
414         public static object GetPropertyValue(object xamlElement, XmlName propertyName, HydrationContext context, IXmlLineInfo lineInfo, out object targetProperty)
415         {
416             var localName = propertyName.LocalName;
417             Exception xpe = null;
418             object value;
419             targetProperty = null;
420
421             //If it's an attached BP, update elementType and propertyName
422             var bpOwnerType = xamlElement.GetType();
423             var attached = GetRealNameAndType(ref bpOwnerType, propertyName.NamespaceURI, ref localName, context, lineInfo);
424             var property = GetBindableProperty(bpOwnerType, localName, lineInfo, false);
425
426             //If it's a BindableProberty, GetValue
427             if (xpe == null && TryGetValue(xamlElement, property, out value, out xpe, out targetProperty))
428                 return value;
429
430             //If it's a normal property, get it
431             if (xpe == null && TryGetProperty(xamlElement, localName, out value, context, out xpe, out targetProperty))
432                 return value;
433
434             xpe = xpe ?? new XamlParseException($"Property {localName} is not found or does not have an accessible getter", lineInfo);
435             if (context.ExceptionHandler != null)
436                 context.ExceptionHandler(xpe);
437             else
438                 throw xpe;
439
440             return null;
441         }
442
443         static bool TryConnectEvent(object element, string localName, bool attached, object value, object rootElement, IXmlLineInfo lineInfo, out Exception exception)
444         {
445             exception = null;
446
447             if (attached)
448                 return false;
449
450             var elementType = element.GetType();
451             var eventInfo = elementType.GetRuntimeEvent(localName);
452             var stringValue = value as string;
453
454             if (eventInfo == null || IsNullOrEmpty(stringValue))
455                 return false;
456
457             var methodInfo = rootElement.GetType().GetRuntimeMethods().FirstOrDefault(mi => mi.Name == (string)value);
458             if (methodInfo == null)
459             {
460                 exception = new XamlParseException($"No method {value} found on type {rootElement.GetType()}", lineInfo);
461                 return false;
462             }
463
464             try
465             {
466                 eventInfo.AddEventHandler(element, methodInfo.CreateDelegate(eventInfo.EventHandlerType, rootElement));
467                 return true;
468             }
469             catch (ArgumentException ae)
470             {
471                 exception = new XamlParseException($"Method {stringValue} does not have the correct signature", lineInfo, ae);
472             }
473             return false;
474         }
475
476         static bool TrySetDynamicResource(object element, BindableProperty property, object value, IXmlLineInfo lineInfo, out Exception exception)
477         {
478             exception = null;
479
480             var elementType = element.GetType();
481             var dynamicResource = value as DynamicResource;
482             var bindable = element as BindableObject;
483
484             if (dynamicResource == null || property == null)
485                 return false;
486
487             if (bindable == null)
488             {
489                 exception = new XamlParseException($"{elementType.Name} is not a BindableObject", lineInfo);
490                 return false;
491             }
492
493             bindable.SetDynamicResource(property, dynamicResource.Key);
494             return true;
495         }
496
497         static bool TrySetBinding(object element, BindableProperty property, object value, IXmlLineInfo lineInfo, out Exception exception)
498         {
499             exception = null;
500
501             var elementType = element.GetType();
502             var binding = value.ConvertTo(typeof(BindingBase), pinfoRetriever: null, serviceProvider: null) as BindingBase;
503             var bindable = element as BindableObject;
504
505             if (binding == null)
506                 return false;
507
508             if (bindable != null && property != null)
509             {
510                 bindable.SetBinding(property, binding);
511                 return true;
512             }
513
514             if (property != null)
515                 exception = new XamlParseException($"{elementType.Name} is not a BindableObject or does not support native bindings", lineInfo);
516
517             return false;
518         }
519
520         static bool TrySetValue(object element, BindableProperty property, bool attached, object value, IXmlLineInfo lineInfo, XamlServiceProvider serviceProvider, out Exception exception)
521         {
522             exception = null;
523
524             var elementType = element.GetType();
525             var bindable = element as BindableObject;
526
527             if (property == null)
528                 return false;
529
530             if (serviceProvider != null && serviceProvider.IProvideValueTarget != null)
531                 ((XamlValueTargetProvider)serviceProvider.IProvideValueTarget).TargetProperty = property;
532
533             Func<MemberInfo> minforetriever;
534             if (attached)
535                 minforetriever = () => property.DeclaringType.GetRuntimeMethod("Get" + property.PropertyName, new[] { typeof(BindableObject) });
536             else
537             {
538                 minforetriever = () => property.DeclaringType.GetRuntimeProperties().LastOrDefault(p => p.Name == property.PropertyName);
539             }
540             //minforetriever = () => property.DeclaringType.GetRuntimeProperty(property.PropertyName);
541             var convertedValue = value.ConvertTo(property.ReturnType, minforetriever, serviceProvider);
542
543             if (bindable != null)
544             {
545                 //SetValue doesn't throw on mismatching type, so check before to get a chance to try the property setting or the collection adding
546                 var nullable = property.ReturnTypeInfo.IsGenericType &&
547                                property.ReturnTypeInfo.GetGenericTypeDefinition() == typeof(Nullable<>);
548                 if ((convertedValue == null && (!property.ReturnTypeInfo.IsValueType || nullable)) ||
549                     (property.ReturnType.IsInstanceOfType(convertedValue)))
550                 {
551                     bindable.SetValue(property, convertedValue);
552                     return true;
553                 }
554
555                 // This might be a collection; see if we can add to it
556                 return TryAddValue(bindable, property, value, serviceProvider);
557             }
558
559             exception = new XamlParseException($"{elementType.Name} is not a BindableObject or does not support setting native BindableProperties", lineInfo);
560             return false;
561         }
562
563         static bool TryGetValue(object element, BindableProperty property, out object value, out Exception exception, out object targetProperty)
564         {
565             exception = null;
566             value = null;
567             targetProperty = property;
568             var elementType = element.GetType();
569             var bindable = element as BindableObject;
570
571             if (property == null)
572                 return false;
573
574             if (bindable == null)
575                 return false;
576
577             value = bindable.GetValue(property);
578             return true;
579         }
580
581         static bool TrySetProperty(object element, BindableProperty property, string localName, object value, XamlServiceProvider serviceProvider, HydrationContext context, out Exception exception)
582         {
583             exception = null;
584
585             var elementType = element.GetType();
586             var propertyInfo = elementType.GetRuntimeProperties().FirstOrDefault(p => p.Name == localName);
587             MethodInfo setter;
588             if (propertyInfo == null || !propertyInfo.CanWrite || (setter = propertyInfo.SetMethod) == null)
589                 return false;
590
591             if (!IsVisibleFrom(setter, context.RootElement))
592                 return false;
593
594             if (property != null && propertyInfo.PropertyType != property.ReturnType)
595                 return false;
596
597             if (serviceProvider != null && serviceProvider.IProvideValueTarget != null)
598                 ((XamlValueTargetProvider)serviceProvider.IProvideValueTarget).TargetProperty = propertyInfo;
599
600             object convertedValue = GetConvertedValue(propertyInfo.PropertyType, value, () => propertyInfo, serviceProvider);
601
602             if (null == convertedValue)
603             {
604                 var methods = propertyInfo.PropertyType.GetMethods().Where(a => a.Name == "op_Implicit");
605
606                 foreach (var method in methods)
607                 {
608                     var paramType = method.GetParameters()[0].ParameterType;
609                     convertedValue = GetConvertedValue(paramType, value, () => propertyInfo, serviceProvider);
610
611                     if (null != convertedValue)
612                     {
613                         var realValue = Activator.CreateInstance(propertyInfo.PropertyType);
614                         convertedValue = method.Invoke(realValue, new object[] { convertedValue });
615
616                         if (null != convertedValue)
617                         {
618                             break;
619                         }
620                     }
621                 }
622             }
623
624             if (null == convertedValue)
625             {
626                 return false;
627             }
628
629             setter.Invoke(element, new object[] { convertedValue });
630             return true;
631         }
632
633         static private object GetConvertedValue(Type valueType, object value, Func<MemberInfo> minfoRetriever, XamlServiceProvider serviceProvider)
634         {
635             object convertedValue = value.ConvertTo(valueType, minfoRetriever, serviceProvider);
636
637             if (convertedValue != null && !valueType.IsInstanceOfType(convertedValue))
638             {
639                 convertedValue = null;
640             }
641
642             return convertedValue;
643         }
644
645         static bool TryGetProperty(object element, string localName, out object value, HydrationContext context, out Exception exception, out object targetProperty)
646         {
647             exception = null;
648             value = null;
649             var elementType = element.GetType();
650             PropertyInfo propertyInfo = null;
651             try
652             {
653                 propertyInfo = elementType.GetRuntimeProperty(localName);
654             }
655             catch (AmbiguousMatchException)
656             {
657                 // Get most derived instance of property
658                 foreach (var property in elementType.GetRuntimeProperties().Where(prop => prop.Name == localName))
659                 {
660                     if (propertyInfo == null || propertyInfo.DeclaringType.IsAssignableFrom(property.DeclaringType))
661                         propertyInfo = property;
662                 }
663             }
664             MethodInfo getter;
665             targetProperty = propertyInfo;
666             if (propertyInfo == null || !propertyInfo.CanRead || (getter = propertyInfo.GetMethod) == null)
667                 return false;
668
669             if (!IsVisibleFrom(getter, context.RootElement))
670                 return false;
671
672             value = getter.Invoke(element, System.Array.Empty<object>());
673             return true;
674         }
675
676         static bool IsVisibleFrom(MethodInfo method, object rootElement)
677         {
678             if (method.IsPublic)
679                 return true;
680             if (method.IsPrivate && method.DeclaringType == rootElement.GetType())
681                 return true;
682             if ((method.IsAssembly || method.IsFamilyOrAssembly) && method.DeclaringType.AssemblyQualifiedName == rootElement.GetType().AssemblyQualifiedName)
683                 return true;
684             if (method.IsFamily && method.DeclaringType.IsAssignableFrom(rootElement.GetType()))
685                 return true;
686             return false;
687         }
688
689         static bool TryAddToProperty(object element, XmlName propertyName, object value, string xKey, IXmlLineInfo lineInfo, XamlServiceProvider serviceProvider, HydrationContext context, out Exception exception)
690         {
691             exception = null;
692
693             object targetProperty;
694             var collection = GetPropertyValue(element, propertyName, context, lineInfo, out targetProperty) as IEnumerable;
695
696             if (collection == null)
697                 return false;
698
699             if (exception == null && TryAddToResourceDictionary(collection as ResourceDictionary, value, xKey, lineInfo, out exception))
700                 return true;
701
702             if (exception != null)
703                 return false;
704
705             var addMethod = collection.GetType().GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1);
706             if (addMethod == null)
707                 return false;
708
709             if (serviceProvider != null && serviceProvider.IProvideValueTarget != null)
710                 ((XamlValueTargetProvider)serviceProvider.IProvideValueTarget).TargetProperty = targetProperty;
711
712             addMethod.Invoke(collection, new[] { value.ConvertTo(addMethod.GetParameters()[0].ParameterType, (Func<TypeConverter>)null, serviceProvider) });
713             return true;
714         }
715
716         static bool TryAddToResourceDictionary(ResourceDictionary resourceDictionary, object value, string xKey, IXmlLineInfo lineInfo, out Exception exception)
717         {
718             exception = null;
719
720             if (resourceDictionary == null)
721                 return false;
722
723             if (xKey != null)
724                 resourceDictionary.Add(xKey, value);
725             else if (value is ResourceDictionary)
726                 resourceDictionary.Add((ResourceDictionary)value);
727             else
728             {
729                 exception = new XamlParseException("resources in ResourceDictionary require a x:Key attribute", lineInfo);
730                 return false;
731             }
732             return true;
733         }
734
735         void SetTemplate(ElementTemplate dt, INode node)
736         {
737 #pragma warning disable 0612
738             ((IDataTemplate)dt).LoadTemplate = () =>
739             {
740 #pragma warning restore 0612
741                 var cnode = node.Clone();
742                 var context = new HydrationContext { ParentContext = Context, RootElement = Context.RootElement };
743                 cnode.Accept(new XamlNodeVisitor((n, parent) => n.Parent = parent), node.Parent); //set parents for {StaticResource}
744                 cnode.Accept(new ExpandMarkupsVisitor(context), null);
745                 cnode.Accept(new NamescopingVisitor(context), null);
746                 cnode.Accept(new CreateValuesVisitor(context), null);
747                 cnode.Accept(new RegisterXNamesVisitor(context), null);
748                 cnode.Accept(new FillResourceDictionariesVisitor(context), null);
749                 cnode.Accept(new ApplyPropertiesVisitor(context, true), null);
750                 return context.Values[cnode];
751             };
752         }
753
754         static bool TryAddValue(BindableObject bindable, BindableProperty property, object value, XamlServiceProvider serviceProvider)
755         {
756             if (property?.ReturnTypeInfo?.GenericTypeArguments == null)
757             {
758                 return false;
759             }
760
761             if (property.ReturnType == null)
762             {
763                 return false;
764             }
765
766             if (property.ReturnTypeInfo.GenericTypeArguments.Length != 1 ||
767                 !property.ReturnTypeInfo.GenericTypeArguments[0].IsInstanceOfType(value))
768                 return false;
769
770             // This might be a collection we can add to; see if we can find an Add method
771             var addMethod = GetAllRuntimeMethods(property.ReturnType)
772                 .FirstOrDefault(mi => mi.Name == "Add" && mi.GetParameters().Length == 1);
773             if (addMethod == null)
774                 return false;
775
776             // If there's an add method, get the collection
777             var collection = bindable.GetValue(property);
778
779             // And add the new value to it
780             addMethod.Invoke(collection, new[] { value.ConvertTo(addMethod.GetParameters()[0].ParameterType, (Func<TypeConverter>)null, serviceProvider) });
781             return true;
782         }
783
784         static IEnumerable<MethodInfo> GetAllRuntimeMethods(Type type)
785         {
786             return type.GetRuntimeMethods()
787                 .Concat(type.GetTypeInfo().ImplementedInterfaces.SelectMany(t => t.GetRuntimeMethods()));
788         }
789
790         bool TrySetRuntimeName(XmlName propertyName, object source, object value, ValueNode node)
791         {
792             if (propertyName != XmlName.xName)
793                 return false;
794
795             var runTimeName = source.GetType().GetTypeInfo().GetCustomAttribute<RuntimeNamePropertyAttribute>();
796             if (runTimeName == null)
797                 return false;
798
799             SetPropertyValue(source, new XmlName("", runTimeName.Name), value, Context.RootElement, node, Context, node);
800             return true;
801         }
802
803         private PropertyInfo GetIndexer(object source, Type keyType, Type valueType) => source.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(p => p.Name == "Item" && p.PropertyType.IsAssignableFrom(valueType) && p.GetIndexParameters().Length != 0 && p.GetIndexParameters()[0].ParameterType == keyType);
804     }
805 }