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