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