Merge remote-tracking branch 'origin/master' into tizen
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / Xaml / CreateValuesVisitor.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.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 element)
163                         {
164                             element.IsCreateByXaml = true;
165                             element.LineNumber = node.LineNumber;
166                             element.LinePosition = node.LinePosition;
167                         }
168                     }
169                 }
170                 catch (TargetInvocationException e)
171                 {
172                     if (e.InnerException is XamlParseException || e.InnerException is XmlException)
173                         throw e.InnerException;
174                     throw;
175                 }
176             }
177
178             Values[node] = value;
179
180             var markup = value as IMarkupExtension;
181             if (markup != null && (value is TypeExtension || value is StaticExtension || value is ArrayExtension))
182             {
183                 var serviceProvider = new XamlServiceProvider(node, Context);
184
185                 var visitor = new ApplyPropertiesVisitor(Context);
186                 foreach (var cnode in node.Properties.Values.ToList())
187                     cnode.Accept(visitor, node);
188                 foreach (var cnode in node.CollectionItems)
189                     cnode.Accept(visitor, node);
190
191                 value = markup.ProvideValue(serviceProvider);
192
193                 INode xKey;
194                 if (!node.Properties.TryGetValue(XmlName.xKey, out xKey))
195                     xKey = null;
196
197                 node.Properties.Clear();
198                 node.CollectionItems.Clear();
199
200                 if (xKey != null)
201                     node.Properties.Add(XmlName.xKey, xKey);
202
203                 Values[node] = value;
204             }
205
206             if (value is BindableObject)
207                 NameScope.SetNameScope(value as BindableObject, node.Namescope);
208         }
209
210         public void Visit(RootNode node, INode parentNode)
211         {
212             var rnode = (XamlLoader.RuntimeRootNode)node;
213             Values[node] = rnode.Root;
214             Context.Types[node] = rnode.Root.GetType();
215             var bindableRoot = rnode.Root as BindableObject;
216             if (bindableRoot != null)
217                 NameScope.SetNameScope(bindableRoot, node.Namescope);
218         }
219
220         public void Visit(ListNode node, INode parentNode)
221         {
222             //this is a gross hack to keep ListNode alive. ListNode must go in favor of Properties
223             XmlName name;
224             if (ApplyPropertiesVisitor.TryGetPropertyName(node, parentNode, out name))
225                 node.XmlName = name;
226         }
227
228         bool ValidateCtorArguments(Type nodeType, IElementNode node, out string missingArgName)
229         {
230             missingArgName = null;
231             var ctorInfo =
232                 nodeType.GetTypeInfo()
233                     .DeclaredConstructors.FirstOrDefault(
234                         ci =>
235                             ci.GetParameters().Length != 0 && ci.IsPublic &&
236                             ci.GetParameters().All(pi => pi.CustomAttributes.Any(attr => attr.AttributeType == typeof(ParameterAttribute))));
237             if (ctorInfo == null)
238                 return true;
239             foreach (var parameter in ctorInfo.GetParameters())
240             {
241                 // Modify the namespace
242                 var propname =
243                     parameter.CustomAttributes.First(ca => ca.AttributeType.FullName == "Tizen.NUI.Binding.ParameterAttribute")?
244                         .ConstructorArguments.First()
245                         .Value as string;
246                 if (!node.Properties.ContainsKey(new XmlName("", propname)))
247                 {
248                     missingArgName = propname;
249                     return false;
250                 }
251             }
252
253             return true;
254         }
255
256         public object CreateFromParameterizedConstructor(Type nodeType, IElementNode node)
257         {
258             var ctorInfo =
259                 nodeType.GetTypeInfo()
260                     .DeclaredConstructors.FirstOrDefault(
261                         ci =>
262                             ci.GetParameters().Length != 0 && ci.IsPublic &&
263                             ci.GetParameters().All(pi => pi.CustomAttributes.Any(attr => attr.AttributeType == typeof(ParameterAttribute))));
264             object[] arguments = CreateArgumentsArray(node, ctorInfo);
265
266             if (arguments != null)
267             {
268                 return ctorInfo?.Invoke(arguments);
269             }
270             else
271             {
272                 return null;
273             }
274         }
275
276         public object CreateFromFactory(Type nodeType, IElementNode node)
277         {
278             object[] arguments = CreateArgumentsArray(node);
279
280             if (!node.Properties.ContainsKey(XmlName.xFactoryMethod))
281             {
282                 //non-default ctor
283                 object ret = Activator.CreateInstance(nodeType, BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.Instance | BindingFlags.OptionalParamBinding, null, arguments, CultureInfo.CurrentCulture);
284                 if (ret is Element element)
285                 {
286                     element.IsCreateByXaml = true;
287                     element.LineNumber = (node as ElementNode)?.LineNumber ?? -1;
288                     element.LinePosition = (node as ElementNode)?.LinePosition ?? -1;
289                 }
290                 return ret;
291             }
292
293             var factoryMethod = ((string)((ValueNode)node.Properties[XmlName.xFactoryMethod]).Value);
294             Type[] types = arguments == null ? System.Array.Empty<Type>() : arguments.Select(a => a.GetType()).ToArray();
295             Func<MethodInfo, bool> isMatch = m =>
296             {
297                 if (m.Name != factoryMethod)
298                     return false;
299                 var p = m.GetParameters();
300                 if (p.Length != types.Length)
301                     return false;
302                 if (!m.IsStatic)
303                     return false;
304                 for (var i = 0; i < p.Length; i++)
305                 {
306                     if ((p[i].ParameterType.IsAssignableFrom(types[i])))
307                         continue;
308                     var op_impl = p[i].ParameterType.GetImplicitConversionOperator(fromType: types[i], toType: p[i].ParameterType)
309                                 ?? types[i].GetImplicitConversionOperator(fromType: types[i], toType: p[i].ParameterType);
310
311                     if (op_impl == null)
312                         return false;
313                     arguments[i] = op_impl.Invoke(null, new[] { arguments[i] });
314                 }
315                 return true;
316             };
317             var mi = nodeType.GetRuntimeMethods().FirstOrDefault(isMatch);
318             if (mi == null)
319             {
320                 if (node is ElementNode elementNode)
321                 {
322                     var nodeTypeExtension = XamlParser.GetElementTypeExtension(node.XmlType, elementNode, Context.RootElement?.GetType().GetTypeInfo().Assembly);
323                     mi = nodeTypeExtension?.GetRuntimeMethods().FirstOrDefault(isMatch);
324                 }
325             }
326
327             if (mi == null)
328             {
329                 throw new MissingMemberException($"No static method found for {nodeType.FullName}::{factoryMethod} ({string.Join(", ", types.Select(t => t.FullName))})");
330             }
331
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 element)
422                 {
423                     element.IsCreateByXaml = true;
424                     element.LineNumber = (node as ElementNode)?.LineNumber ?? -1;
425                     element.LinePosition = (node as ElementNode)?.LinePosition ?? -1;
426                 }
427             }
428
429             if (node.CollectionItems.Count == 1 && node.CollectionItems[0] is ValueNode &&
430                 ((ValueNode)node.CollectionItems[0]).Value is string)
431             {
432                 var valuestring = ((ValueNode)node.CollectionItems[0]).Value as string;
433
434                 if (nodeType == typeof(SByte))
435                 {
436                     sbyte retval;
437                     if (sbyte.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
438                         return retval;
439                 }
440                 if (nodeType == typeof(Int16))
441                 {
442                     return Convert.ToInt16(GraphicsTypeManager.Instance.ConvertScriptToPixel(valuestring));
443                 }
444                 if (nodeType == typeof(Int32))
445                 {
446                     return Convert.ToInt32(GraphicsTypeManager.Instance.ConvertScriptToPixel(valuestring));
447                 }
448                 if (nodeType == typeof(Int64))
449                 {
450                     return Convert.ToInt64(GraphicsTypeManager.Instance.ConvertScriptToPixel(valuestring));
451                 }
452                 if (nodeType == typeof(Byte))
453                 {
454                     byte retval;
455                     if (byte.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
456                         return retval;
457                 }
458                 if (nodeType == typeof(UInt16))
459                 {
460                     return Convert.ToUInt16(GraphicsTypeManager.Instance.ConvertScriptToPixel(valuestring));
461                 }
462                 if (nodeType == typeof(UInt32))
463                 {
464                     return Convert.ToUInt32(GraphicsTypeManager.Instance.ConvertScriptToPixel(valuestring));
465                 }
466                 if (nodeType == typeof(UInt64))
467                 {
468                     return Convert.ToUInt64(GraphicsTypeManager.Instance.ConvertScriptToPixel(valuestring));
469                 }
470                 if (nodeType == typeof(Single))
471                 {
472                     return GraphicsTypeManager.Instance.ConvertScriptToPixel(valuestring);
473                 }
474                 if (nodeType == typeof(Double))
475                 {
476                     return Convert.ToDouble(GraphicsTypeManager.Instance.ConvertScriptToPixel(valuestring));
477                 }
478                 if (nodeType == typeof(Boolean))
479                 {
480                     bool outbool;
481                     if (bool.TryParse(valuestring, out outbool))
482                         return outbool;
483                 }
484                 if (nodeType == typeof(TimeSpan))
485                 {
486                     TimeSpan retval;
487                     if (TimeSpan.TryParse(valuestring, CultureInfo.InvariantCulture, out retval))
488                         return retval;
489                 }
490                 if (nodeType == typeof(char))
491                 {
492                     char retval;
493                     if (char.TryParse(valuestring, out retval))
494                         return retval;
495                 }
496                 if (nodeType == typeof(string))
497                     return valuestring;
498                 if (nodeType == typeof(decimal))
499                 {
500                     decimal retval;
501                     if (decimal.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
502                         return retval;
503                 }
504
505                 else if (nodeType == typeof(Uri))
506                 {
507                     Uri retval;
508                     if (Uri.TryCreate(valuestring, UriKind.RelativeOrAbsolute, out retval))
509                         return retval;
510                 }
511             }
512             return value;
513         }
514     }
515 }