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