Release 9.0.0.16429
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / Xaml / CreateValuesVisitor.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.Generic;
20 using System.Globalization;
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
28 namespace Tizen.NUI.Xaml
29 {
30     internal class CreateValuesVisitor : IXamlNodeVisitor
31     {
32         public CreateValuesVisitor(HydrationContext context)
33         {
34             Context = context;
35         }
36
37         Dictionary<INode, object> Values
38         {
39             get { return Context.Values; }
40         }
41
42         HydrationContext Context { get; }
43
44         public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp;
45         public bool StopOnDataTemplate => true;
46         public bool StopOnResourceDictionary => false;
47         public bool VisitNodeOnDataTemplate => false;
48         public bool SkipChildren(INode node, INode parentNode) => false;
49         public bool IsResourceDictionary(ElementNode node) => typeof(ResourceDictionary).IsAssignableFrom(Context.Types[node]);
50
51         public void Visit(ValueNode node, INode parentNode)
52         {
53             Values[node] = node.Value;
54         }
55
56         public void Visit(MarkupNode node, INode parentNode)
57         {
58         }
59
60         public void Visit(ElementNode node, INode parentNode)
61         {
62             object value = null;
63
64             XamlParseException xpe;
65             var type = XamlParser.GetElementType(node.XmlType, node, Context.RootElement?.GetType().GetTypeInfo().Assembly,
66                 out xpe);
67             if (type == null)
68                 throw new ArgumentNullException(null, "type should not be null");
69             if (xpe != null)
70                 throw xpe;
71
72             Context.Types[node] = type;
73             string ctorargname;
74             if (IsXaml2009LanguagePrimitive(node))
75                 value = CreateLanguagePrimitive(type, node);
76             else if (node.Properties.ContainsKey(XmlName.xArguments) || node.Properties.ContainsKey(XmlName.xFactoryMethod))
77                 value = CreateFromFactory(type, node);
78             else if (
79                 type.GetTypeInfo()
80                     .DeclaredConstructors.Any(
81                         ci =>
82                             ci.IsPublic && ci.GetParameters().Length != 0 &&
83                             ci.GetParameters().All(pi => pi.CustomAttributes.Any(attr => attr.AttributeType == typeof(ParameterAttribute)))) &&
84                 ValidateCtorArguments(type, node, out ctorargname))
85                 value = CreateFromParameterizedConstructor(type, node);
86             else if (!type.GetTypeInfo().DeclaredConstructors.Any(ci => ci.IsPublic && ci.GetParameters().Length == 0) &&
87                      !ValidateCtorArguments(type, node, out ctorargname))
88             {
89                 throw new XamlParseException($"The Property {ctorargname} is required to create a {type?.FullName} object.", node);
90             }
91             else
92             {
93                 //this is a trick as the DataTemplate parameterless ctor is internal, and we can't CreateInstance(..., false) on WP7
94                 try
95                 {
96                     if (type == typeof(DataTemplate))
97                         value = new DataTemplate();
98                     if (type == typeof(ControlTemplate))
99                         value = new ControlTemplate();
100                     if (value == null && node.CollectionItems.Any() && node.CollectionItems.First() is ValueNode)
101                     {
102                         var serviceProvider = new XamlServiceProvider(node, Context);
103                         var converted = ((ValueNode)node.CollectionItems.First()).Value.ConvertTo(type, () => type.GetTypeInfo(),
104                             serviceProvider);
105                         if (converted != null && converted.GetType() == type)
106                             value = converted;
107                     }
108                     if (value == null)
109                     {
110                         if (type.GetTypeInfo().DeclaredConstructors.Any(ci => ci.IsPublic && ci.GetParameters().Length == 0))
111                         {
112                             //default constructor
113                             value = Activator.CreateInstance(type);
114                         }
115                         else
116                         {
117                             ConstructorInfo constructorInfo = null;
118
119                             //constructor with all default parameters
120                             foreach (var constructor in type.GetConstructors())
121                             {
122                                 if (!constructor.IsStatic)
123                                 {
124                                     bool areAllParamsDefault = true;
125
126                                     foreach (var param in constructor.GetParameters())
127                                     {
128                                         if (!param.HasDefaultValue)
129                                         {
130                                             areAllParamsDefault = false;
131                                             break;
132                                         }
133                                     }
134
135                                     if (areAllParamsDefault)
136                                     {
137                                         if (null == constructorInfo)
138                                         {
139                                             constructorInfo = constructor;
140                                         }
141                                         else
142                                         {
143                                             throw new XamlParseException($"{type.FullName} has more than one constructor which params are all default.", node);
144                                         }
145                                     }
146                                 }
147                             }
148
149                             if (null == constructorInfo)
150                             {
151                                 throw new XamlParseException($"{type.FullName} has no constructor which params are all default.", node);
152                             }
153
154                             List<object> defaultParams = new List<object>();
155                             foreach (var param in constructorInfo.GetParameters())
156                             {
157                                 defaultParams.Add(param.DefaultValue);
158                             }
159
160                             value = Activator.CreateInstance(type, defaultParams.ToArray());
161                         }
162                         if (value is Element)
163                         {
164                             if (null != Application.Current)
165                             {
166                                 Application.AddResourceChangedCallback(value, (value as Element).OnResourcesChanged);
167                             }
168
169                             if (value is BindableObject)
170                             {
171                                 ((BindableObject)value).IsCreateByXaml = true;
172                             }
173                         }
174                     }
175                 }
176                 catch (TargetInvocationException e)
177                 {
178                     if (e.InnerException is XamlParseException || e.InnerException is XmlException)
179                         throw e.InnerException;
180                     throw;
181                 }
182             }
183
184             Values[node] = value;
185
186             var markup = value as IMarkupExtension;
187             if (markup != null && (value is TypeExtension || value is StaticExtension || value is ArrayExtension))
188             {
189                 var serviceProvider = new XamlServiceProvider(node, Context);
190
191                 var visitor = new ApplyPropertiesVisitor(Context);
192                 foreach (var cnode in node.Properties.Values.ToList())
193                     cnode.Accept(visitor, node);
194                 foreach (var cnode in node.CollectionItems)
195                     cnode.Accept(visitor, node);
196
197                 value = markup.ProvideValue(serviceProvider);
198
199                 INode xKey;
200                 if (!node.Properties.TryGetValue(XmlName.xKey, out xKey))
201                     xKey = null;
202
203                 node.Properties.Clear();
204                 node.CollectionItems.Clear();
205
206                 if (xKey != null)
207                     node.Properties.Add(XmlName.xKey, xKey);
208
209                 Values[node] = value;
210             }
211
212             if (value is BindableObject)
213                 NameScope.SetNameScope(value as BindableObject, node.Namescope);
214         }
215
216         public void Visit(RootNode node, INode parentNode)
217         {
218             var rnode = (XamlLoader.RuntimeRootNode)node;
219             Values[node] = rnode.Root;
220             Context.Types[node] = rnode.Root.GetType();
221             var bindableRoot = rnode.Root as BindableObject;
222             if (bindableRoot != null)
223                 NameScope.SetNameScope(bindableRoot, node.Namescope);
224         }
225
226         public void Visit(ListNode node, INode parentNode)
227         {
228             //this is a gross hack to keep ListNode alive. ListNode must go in favor of Properties
229             XmlName name;
230             if (ApplyPropertiesVisitor.TryGetPropertyName(node, parentNode, out name))
231                 node.XmlName = name;
232         }
233
234         bool ValidateCtorArguments(Type nodeType, IElementNode node, out string missingArgName)
235         {
236             missingArgName = null;
237             var ctorInfo =
238                 nodeType.GetTypeInfo()
239                     .DeclaredConstructors.FirstOrDefault(
240                         ci =>
241                             ci.GetParameters().Length != 0 && ci.IsPublic &&
242                             ci.GetParameters().All(pi => pi.CustomAttributes.Any(attr => attr.AttributeType == typeof(ParameterAttribute))));
243             if (ctorInfo == null)
244                 return true;
245             foreach (var parameter in ctorInfo.GetParameters())
246             {
247                 // Modify the namespace
248                 var propname =
249                     parameter.CustomAttributes.First(ca => ca.AttributeType.FullName == "Tizen.NUI.Binding.ParameterAttribute")?
250                         .ConstructorArguments.First()
251                         .Value as string;
252                 if (!node.Properties.ContainsKey(new XmlName("", propname)))
253                 {
254                     missingArgName = propname;
255                     return false;
256                 }
257             }
258
259             return true;
260         }
261
262         public object CreateFromParameterizedConstructor(Type nodeType, IElementNode node)
263         {
264             var ctorInfo =
265                 nodeType.GetTypeInfo()
266                     .DeclaredConstructors.FirstOrDefault(
267                         ci =>
268                             ci.GetParameters().Length != 0 && ci.IsPublic &&
269                             ci.GetParameters().All(pi => pi.CustomAttributes.Any(attr => attr.AttributeType == typeof(ParameterAttribute))));
270             object[] arguments = CreateArgumentsArray(node, ctorInfo);
271
272             if (arguments != null)
273             {
274                 return ctorInfo?.Invoke(arguments);
275             }
276             else
277             {
278                 return null;
279             }
280         }
281
282         public object CreateFromFactory(Type nodeType, IElementNode node)
283         {
284             object[] arguments = CreateArgumentsArray(node);
285
286             if (!node.Properties.ContainsKey(XmlName.xFactoryMethod))
287             {
288                 //non-default ctor
289                 object ret = Activator.CreateInstance(nodeType, BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.Instance | BindingFlags.OptionalParamBinding, null, arguments, CultureInfo.CurrentCulture);
290                 if (ret is Element)
291                 {
292                     if (null != Application.Current)
293                     {
294                         Application.AddResourceChangedCallback(ret, (ret as Element).OnResourcesChanged);
295                     }
296
297                     if (ret is BindableObject)
298                     {
299                         ((BindableObject)ret).IsCreateByXaml = true;
300                     }
301                 }
302                 return ret;
303             }
304
305             var factoryMethod = ((string)((ValueNode)node.Properties[XmlName.xFactoryMethod]).Value);
306             Type[] types = arguments == null ? System.Array.Empty<Type>() : arguments.Select(a => a.GetType()).ToArray();
307             Func<MethodInfo, bool> isMatch = m =>
308             {
309                 if (m.Name != factoryMethod)
310                     return false;
311                 var p = m.GetParameters();
312                 if (p.Length != types.Length)
313                     return false;
314                 if (!m.IsStatic)
315                     return false;
316                 for (var i = 0; i < p.Length; i++)
317                 {
318                     if ((p[i].ParameterType.IsAssignableFrom(types[i])))
319                         continue;
320                     var op_impl = p[i].ParameterType.GetImplicitConversionOperator(fromType: types[i], toType: p[i].ParameterType)
321                                 ?? types[i].GetImplicitConversionOperator(fromType: types[i], toType: p[i].ParameterType);
322
323                     if (op_impl == null)
324                         return false;
325                     arguments[i] = op_impl.Invoke(null, new[] { arguments[i] });
326                 }
327                 return true;
328             };
329             var mi = nodeType.GetRuntimeMethods().FirstOrDefault(isMatch);
330             if (mi == null)
331                 throw new MissingMemberException($"No static method found for {nodeType.FullName}::{factoryMethod} ({string.Join(", ", types.Select(t => t.FullName))})");
332             return mi.Invoke(null, arguments);
333         }
334
335         public object[] CreateArgumentsArray(IElementNode enode)
336         {
337             if (!enode.Properties.ContainsKey(XmlName.xArguments))
338                 return null;
339             var node = enode.Properties[XmlName.xArguments];
340             var elementNode = node as ElementNode;
341             if (elementNode != null)
342             {
343                 var array = new object[1];
344                 array[0] = Values[elementNode];
345
346                 if (array[0].GetType().IsClass)
347                 {
348                     elementNode.Accept(new ApplyPropertiesVisitor(Context, true), null);
349                 }
350
351                 return array;
352             }
353
354             var listnode = node as ListNode;
355             if (listnode != null)
356             {
357                 var array = new object[listnode.CollectionItems.Count];
358                 for (var i = 0; i < listnode.CollectionItems.Count; i++)
359                     array[i] = Values[(ElementNode)listnode.CollectionItems[i]];
360                 return array;
361             }
362             return null;
363         }
364
365         public object[] CreateArgumentsArray(IElementNode enode, ConstructorInfo ctorInfo)
366         {
367             if (ctorInfo != null)
368             {
369                 var n = ctorInfo.GetParameters().Length;
370                 var array = new object[n];
371                 for (var i = 0; i < n; i++)
372                 {
373                     var parameter = ctorInfo.GetParameters()[i];
374                     var propname =
375                         parameter?.CustomAttributes?.First(attr => attr.AttributeType == typeof(ParameterAttribute))?
376                             .ConstructorArguments.First()
377                             .Value as string;
378                     var name = new XmlName("", propname);
379                     INode node;
380                     if (!enode.Properties.TryGetValue(name, out node))
381                     {
382                         String msg = "";
383                         if (propname != null)
384                         {
385                             msg = String.Format("The Property {0} is required to create a {1} object.", propname, ctorInfo.DeclaringType.FullName);
386                         }
387                         else
388                         {
389                             msg = "propname is null.";
390                         }
391                         throw new XamlParseException(msg, enode as IXmlLineInfo);
392                     }
393                     if (!enode.SkipProperties.Contains(name))
394                         enode.SkipProperties.Add(name);
395                     var value = Context.Values[node];
396                     var serviceProvider = new XamlServiceProvider(enode, Context);
397                     var convertedValue = value?.ConvertTo(parameter?.ParameterType, () => parameter, serviceProvider);
398                     array[i] = convertedValue;
399                 }
400                 return array;
401             }
402
403             return null;
404         }
405
406         static bool IsXaml2009LanguagePrimitive(IElementNode node)
407         {
408             return node.NamespaceURI == XamlParser.X2009Uri;
409         }
410
411         static object CreateLanguagePrimitive(Type nodeType, IElementNode node)
412         {
413             object value = null;
414             if (nodeType == typeof(string))
415                 value = String.Empty;
416             else if (nodeType == typeof(Uri))
417                 value = null;
418             else
419             {
420                 value = Activator.CreateInstance(nodeType);
421                 if (value is Element)
422                 {
423                     if (null != Application.Current)
424                     {
425                         Application.AddResourceChangedCallback(value, (value as Element).OnResourcesChanged);
426                     }
427
428                     if (value is BindableObject)
429                     {
430                         ((BindableObject)value).IsCreateByXaml = true;
431                     }
432                 }
433             }
434
435             if (node.CollectionItems.Count == 1 && node.CollectionItems[0] is ValueNode &&
436                 ((ValueNode)node.CollectionItems[0]).Value is string)
437             {
438                 var valuestring = ((ValueNode)node.CollectionItems[0]).Value as string;
439
440                 if (nodeType == typeof(SByte))
441                 {
442                     sbyte retval;
443                     if (sbyte.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
444                         return retval;
445                 }
446                 if (nodeType == typeof(Int16))
447                 {
448                     short retval;
449                     if (short.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
450                         return retval;
451                 }
452                 if (nodeType == typeof(Int32))
453                 {
454                     int retval;
455                     if (int.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
456                         return retval;
457                 }
458                 if (nodeType == typeof(Int64))
459                 {
460                     long retval;
461                     if (long.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
462                         return retval;
463                 }
464                 if (nodeType == typeof(Byte))
465                 {
466                     byte retval;
467                     if (byte.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
468                         return retval;
469                 }
470                 if (nodeType == typeof(UInt16))
471                 {
472                     ushort retval;
473                     if (ushort.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
474                         return retval;
475                 }
476                 if (nodeType == typeof(UInt32))
477                 {
478                     uint retval;
479                     if (uint.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
480                         return retval;
481                 }
482                 if (nodeType == typeof(UInt64))
483                 {
484                     ulong retval;
485                     if (ulong.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
486                         return retval;
487                 }
488                 if (nodeType == typeof(Single))
489                 {
490                     float retval;
491                     if (float.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
492                         return retval;
493                 }
494                 if (nodeType == typeof(Double))
495                 {
496                     double retval;
497                     if (double.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
498                         return retval;
499                 }
500                 if (nodeType == typeof(Boolean))
501                 {
502                     bool outbool;
503                     if (bool.TryParse(valuestring, out outbool))
504                         return outbool;
505                 }
506                 if (nodeType == typeof(TimeSpan))
507                 {
508                     TimeSpan retval;
509                     if (TimeSpan.TryParse(valuestring, CultureInfo.InvariantCulture, out retval))
510                         return retval;
511                 }
512                 if (nodeType == typeof(char))
513                 {
514                     char retval;
515                     if (char.TryParse(valuestring, out retval))
516                         return retval;
517                 }
518                 if (nodeType == typeof(string))
519                     return valuestring;
520                 if (nodeType == typeof(decimal))
521                 {
522                     decimal retval;
523                     if (decimal.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
524                         return retval;
525                 }
526
527                 else if (nodeType == typeof(Uri))
528                 {
529                     Uri retval;
530                     if (Uri.TryCreate(valuestring, UriKind.RelativeOrAbsolute, out retval))
531                         return retval;
532                 }
533             }
534             return value;
535         }
536     }
537 }