Release 10.0.0.16997
[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 element)
163                         {
164                             if (null != Application.Current)
165                             {
166                                 Application.Current.XamlResourceChanged += element.OnResourcesChanged;
167                             }
168
169                             element.IsCreateByXaml = true;
170                             element.LineNumber = node.LineNumber;
171                             element.LinePosition = node.LinePosition;
172                         }
173                     }
174                 }
175                 catch (TargetInvocationException e)
176                 {
177                     if (e.InnerException is XamlParseException || e.InnerException is XmlException)
178                         throw e.InnerException;
179                     throw;
180                 }
181             }
182
183             Values[node] = value;
184
185             var markup = value as IMarkupExtension;
186             if (markup != null && (value is TypeExtension || value is StaticExtension || value is ArrayExtension))
187             {
188                 var serviceProvider = new XamlServiceProvider(node, Context);
189
190                 var visitor = new ApplyPropertiesVisitor(Context);
191                 foreach (var cnode in node.Properties.Values.ToList())
192                     cnode.Accept(visitor, node);
193                 foreach (var cnode in node.CollectionItems)
194                     cnode.Accept(visitor, node);
195
196                 value = markup.ProvideValue(serviceProvider);
197
198                 INode xKey;
199                 if (!node.Properties.TryGetValue(XmlName.xKey, out xKey))
200                     xKey = null;
201
202                 node.Properties.Clear();
203                 node.CollectionItems.Clear();
204
205                 if (xKey != null)
206                     node.Properties.Add(XmlName.xKey, xKey);
207
208                 Values[node] = value;
209             }
210
211             if (value is BindableObject)
212                 NameScope.SetNameScope(value as BindableObject, node.Namescope);
213         }
214
215         public void Visit(RootNode node, INode parentNode)
216         {
217             var rnode = (XamlLoader.RuntimeRootNode)node;
218             Values[node] = rnode.Root;
219             Context.Types[node] = rnode.Root.GetType();
220             var bindableRoot = rnode.Root as BindableObject;
221             if (bindableRoot != null)
222                 NameScope.SetNameScope(bindableRoot, node.Namescope);
223         }
224
225         public void Visit(ListNode node, INode parentNode)
226         {
227             //this is a gross hack to keep ListNode alive. ListNode must go in favor of Properties
228             XmlName name;
229             if (ApplyPropertiesVisitor.TryGetPropertyName(node, parentNode, out name))
230                 node.XmlName = name;
231         }
232
233         bool ValidateCtorArguments(Type nodeType, IElementNode node, out string missingArgName)
234         {
235             missingArgName = null;
236             var ctorInfo =
237                 nodeType.GetTypeInfo()
238                     .DeclaredConstructors.FirstOrDefault(
239                         ci =>
240                             ci.GetParameters().Length != 0 && ci.IsPublic &&
241                             ci.GetParameters().All(pi => pi.CustomAttributes.Any(attr => attr.AttributeType == typeof(ParameterAttribute))));
242             if (ctorInfo == null)
243                 return true;
244             foreach (var parameter in ctorInfo.GetParameters())
245             {
246                 // Modify the namespace
247                 var propname =
248                     parameter.CustomAttributes.First(ca => ca.AttributeType.FullName == "Tizen.NUI.Binding.ParameterAttribute")?
249                         .ConstructorArguments.First()
250                         .Value as string;
251                 if (!node.Properties.ContainsKey(new XmlName("", propname)))
252                 {
253                     missingArgName = propname;
254                     return false;
255                 }
256             }
257
258             return true;
259         }
260
261         public object CreateFromParameterizedConstructor(Type nodeType, IElementNode node)
262         {
263             var ctorInfo =
264                 nodeType.GetTypeInfo()
265                     .DeclaredConstructors.FirstOrDefault(
266                         ci =>
267                             ci.GetParameters().Length != 0 && ci.IsPublic &&
268                             ci.GetParameters().All(pi => pi.CustomAttributes.Any(attr => attr.AttributeType == typeof(ParameterAttribute))));
269             object[] arguments = CreateArgumentsArray(node, ctorInfo);
270
271             if (arguments != null)
272             {
273                 return ctorInfo?.Invoke(arguments);
274             }
275             else
276             {
277                 return null;
278             }
279         }
280
281         public object CreateFromFactory(Type nodeType, IElementNode node)
282         {
283             object[] arguments = CreateArgumentsArray(node);
284
285             if (!node.Properties.ContainsKey(XmlName.xFactoryMethod))
286             {
287                 //non-default ctor
288                 object ret = Activator.CreateInstance(nodeType, BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.Instance | BindingFlags.OptionalParamBinding, null, arguments, CultureInfo.CurrentCulture);
289                 if (ret is Element element)
290                 {
291                     if (null != Application.Current)
292                     {
293                         Application.Current.XamlResourceChanged += element.OnResourcesChanged;
294                     }
295
296                     element.IsCreateByXaml = true;
297                     element.LineNumber = (node as ElementNode)?.LineNumber ?? -1;
298                     element.LinePosition = (node as ElementNode)?.LinePosition ?? -1;
299                 }
300                 return ret;
301             }
302
303             var factoryMethod = ((string)((ValueNode)node.Properties[XmlName.xFactoryMethod]).Value);
304             Type[] types = arguments == null ? System.Array.Empty<Type>() : arguments.Select(a => a.GetType()).ToArray();
305             Func<MethodInfo, bool> isMatch = m =>
306             {
307                 if (m.Name != factoryMethod)
308                     return false;
309                 var p = m.GetParameters();
310                 if (p.Length != types.Length)
311                     return false;
312                 if (!m.IsStatic)
313                     return false;
314                 for (var i = 0; i < p.Length; i++)
315                 {
316                     if ((p[i].ParameterType.IsAssignableFrom(types[i])))
317                         continue;
318                     var op_impl = p[i].ParameterType.GetImplicitConversionOperator(fromType: types[i], toType: p[i].ParameterType)
319                                 ?? types[i].GetImplicitConversionOperator(fromType: types[i], toType: p[i].ParameterType);
320
321                     if (op_impl == null)
322                         return false;
323                     arguments[i] = op_impl.Invoke(null, new[] { arguments[i] });
324                 }
325                 return true;
326             };
327             var mi = nodeType.GetRuntimeMethods().FirstOrDefault(isMatch);
328             if (mi == null)
329             {
330                 if (node is ElementNode elementNode)
331                 {
332                     var nodeTypeExtension = XamlParser.GetElementTypeExtension(node.XmlType, elementNode, Context.RootElement?.GetType().GetTypeInfo().Assembly);
333                     mi = nodeTypeExtension?.GetRuntimeMethods().FirstOrDefault(isMatch);
334                 }
335             }
336
337             if (mi == null)
338             {
339                 throw new MissingMemberException($"No static method found for {nodeType.FullName}::{factoryMethod} ({string.Join(", ", types.Select(t => t.FullName))})");
340             }
341
342             return mi.Invoke(null, arguments);
343         }
344
345         public object[] CreateArgumentsArray(IElementNode enode)
346         {
347             if (!enode.Properties.ContainsKey(XmlName.xArguments))
348                 return null;
349             var node = enode.Properties[XmlName.xArguments];
350             var elementNode = node as ElementNode;
351             if (elementNode != null)
352             {
353                 var array = new object[1];
354                 array[0] = Values[elementNode];
355
356                 if (array[0].GetType().IsClass)
357                 {
358                     elementNode.Accept(new ApplyPropertiesVisitor(Context, true), null);
359                 }
360
361                 return array;
362             }
363
364             var listnode = node as ListNode;
365             if (listnode != null)
366             {
367                 var array = new object[listnode.CollectionItems.Count];
368                 for (var i = 0; i < listnode.CollectionItems.Count; i++)
369                     array[i] = Values[(ElementNode)listnode.CollectionItems[i]];
370                 return array;
371             }
372             return null;
373         }
374
375         public object[] CreateArgumentsArray(IElementNode enode, ConstructorInfo ctorInfo)
376         {
377             if (ctorInfo != null)
378             {
379                 var n = ctorInfo.GetParameters().Length;
380                 var array = new object[n];
381                 for (var i = 0; i < n; i++)
382                 {
383                     var parameter = ctorInfo.GetParameters()[i];
384                     var propname =
385                         parameter?.CustomAttributes?.First(attr => attr.AttributeType == typeof(ParameterAttribute))?
386                             .ConstructorArguments.First()
387                             .Value as string;
388                     var name = new XmlName("", propname);
389                     INode node;
390                     if (!enode.Properties.TryGetValue(name, out node))
391                     {
392                         String msg = "";
393                         if (propname != null)
394                         {
395                             msg = String.Format("The Property {0} is required to create a {1} object.", propname, ctorInfo.DeclaringType.FullName);
396                         }
397                         else
398                         {
399                             msg = "propname is null.";
400                         }
401                         throw new XamlParseException(msg, enode as IXmlLineInfo);
402                     }
403                     if (!enode.SkipProperties.Contains(name))
404                         enode.SkipProperties.Add(name);
405                     var value = Context.Values[node];
406                     var serviceProvider = new XamlServiceProvider(enode, Context);
407                     var convertedValue = value?.ConvertTo(parameter?.ParameterType, () => parameter, serviceProvider);
408                     array[i] = convertedValue;
409                 }
410                 return array;
411             }
412
413             return null;
414         }
415
416         static bool IsXaml2009LanguagePrimitive(IElementNode node)
417         {
418             return node.NamespaceURI == XamlParser.X2009Uri;
419         }
420
421         static object CreateLanguagePrimitive(Type nodeType, IElementNode node)
422         {
423             object value = null;
424             if (nodeType == typeof(string))
425                 value = String.Empty;
426             else if (nodeType == typeof(Uri))
427                 value = null;
428             else
429             {
430                 value = Activator.CreateInstance(nodeType);
431                 if (value is Element element)
432                 {
433                     if (null != Application.Current)
434                     {
435                         Application.Current.XamlResourceChanged += element.OnResourcesChanged;
436                     }
437
438                     element.IsCreateByXaml = true;
439                     element.LineNumber = (node as ElementNode)?.LineNumber ?? -1;
440                     element.LinePosition = (node as ElementNode)?.LinePosition ?? -1;
441                 }
442             }
443
444             if (node.CollectionItems.Count == 1 && node.CollectionItems[0] is ValueNode &&
445                 ((ValueNode)node.CollectionItems[0]).Value is string)
446             {
447                 var valuestring = ((ValueNode)node.CollectionItems[0]).Value as string;
448
449                 if (nodeType == typeof(SByte))
450                 {
451                     sbyte retval;
452                     if (sbyte.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
453                         return retval;
454                 }
455                 if (nodeType == typeof(Int16))
456                 {
457                     return Convert.ToInt16(GraphicsTypeManager.Instance.ConvertScriptToPixel(valuestring));
458                 }
459                 if (nodeType == typeof(Int32))
460                 {
461                     return Convert.ToInt32(GraphicsTypeManager.Instance.ConvertScriptToPixel(valuestring));
462                 }
463                 if (nodeType == typeof(Int64))
464                 {
465                     return Convert.ToInt64(GraphicsTypeManager.Instance.ConvertScriptToPixel(valuestring));
466                 }
467                 if (nodeType == typeof(Byte))
468                 {
469                     byte retval;
470                     if (byte.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
471                         return retval;
472                 }
473                 if (nodeType == typeof(UInt16))
474                 {
475                     return Convert.ToUInt16(GraphicsTypeManager.Instance.ConvertScriptToPixel(valuestring));
476                 }
477                 if (nodeType == typeof(UInt32))
478                 {
479                     return Convert.ToUInt32(GraphicsTypeManager.Instance.ConvertScriptToPixel(valuestring));
480                 }
481                 if (nodeType == typeof(UInt64))
482                 {
483                     return Convert.ToUInt64(GraphicsTypeManager.Instance.ConvertScriptToPixel(valuestring));
484                 }
485                 if (nodeType == typeof(Single))
486                 {
487                     return GraphicsTypeManager.Instance.ConvertScriptToPixel(valuestring);
488                 }
489                 if (nodeType == typeof(Double))
490                 {
491                     return Convert.ToDouble(GraphicsTypeManager.Instance.ConvertScriptToPixel(valuestring));
492                 }
493                 if (nodeType == typeof(Boolean))
494                 {
495                     bool outbool;
496                     if (bool.TryParse(valuestring, out outbool))
497                         return outbool;
498                 }
499                 if (nodeType == typeof(TimeSpan))
500                 {
501                     TimeSpan retval;
502                     if (TimeSpan.TryParse(valuestring, CultureInfo.InvariantCulture, out retval))
503                         return retval;
504                 }
505                 if (nodeType == typeof(char))
506                 {
507                     char retval;
508                     if (char.TryParse(valuestring, out retval))
509                         return retval;
510                 }
511                 if (nodeType == typeof(string))
512                     return valuestring;
513                 if (nodeType == typeof(decimal))
514                 {
515                     decimal retval;
516                     if (decimal.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
517                         return retval;
518                 }
519
520                 else if (nodeType == typeof(Uri))
521                 {
522                     Uri retval;
523                     if (Uri.TryCreate(valuestring, UriKind.RelativeOrAbsolute, out retval))
524                         return retval;
525                 }
526             }
527             return value;
528         }
529     }
530 }