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