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