[NUI] fixed build warning CA2208 (#2228)
[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                 if (m.Name != factoryMethod)
250                     return false;
251                 var p = m.GetParameters();
252                 if (p.Length != types.Length)
253                     return false;
254                 if (!m.IsStatic)
255                     return false;
256                 for (var i = 0; i < p.Length; i++) {
257                     if ((p [i].ParameterType.IsAssignableFrom(types [i])))
258                         continue;
259                     var op_impl =  p[i].ParameterType.GetImplicitConversionOperator(fromType: types[i], toType: p[i].ParameterType)
260                                 ?? types[i].GetImplicitConversionOperator(fromType: types[i], toType: p[i].ParameterType);
261
262                     if (op_impl == null)
263                         return false;
264                     arguments [i] = op_impl.Invoke(null, new [] { arguments [i]});
265                 }
266                 return true;
267             };
268             var mi = nodeType.GetRuntimeMethods().FirstOrDefault(isMatch);
269             if (mi == null)
270                 throw new MissingMemberException($"No static method found for {nodeType.FullName}::{factoryMethod} ({string.Join(", ", types.Select(t => t.FullName))})");
271             return mi.Invoke(null, arguments);
272         }
273
274         public object[] CreateArgumentsArray(IElementNode enode)
275         {
276             if (!enode.Properties.ContainsKey(XmlName.xArguments))
277                 return null;
278             var node = enode.Properties[XmlName.xArguments];
279             var elementNode = node as ElementNode;
280             if (elementNode != null)
281             {
282                 var array = new object[1];
283                 array[0] = Values[elementNode];
284
285                 if (array[0].GetType().IsClass)
286                 {
287                     elementNode.Accept(new ApplyPropertiesVisitor(Context, true), null);
288                 }
289
290                 return array;
291             }
292
293             var listnode = node as ListNode;
294             if (listnode != null)
295             {
296                 var array = new object[listnode.CollectionItems.Count];
297                 for (var i = 0; i < listnode.CollectionItems.Count; i++)
298                     array[i] = Values[(ElementNode)listnode.CollectionItems[i]];
299                 return array;
300             }
301             return null;
302         }
303
304         public object[] CreateArgumentsArray(IElementNode enode, ConstructorInfo ctorInfo)
305         {
306             if( ctorInfo != null )
307             {
308                 var n = ctorInfo.GetParameters().Length;
309                 var array = new object[n];
310                 for (var i = 0; i < n; i++)
311                 {
312                     var parameter = ctorInfo.GetParameters()[i];
313                     var propname =
314                         parameter?.CustomAttributes?.First(attr => attr.AttributeType == typeof (ParameterAttribute))?
315                             .ConstructorArguments.First()
316                             .Value as string;
317                     var name = new XmlName("", propname);
318                     INode node;
319                     if (!enode.Properties.TryGetValue(name, out node))
320                     {
321                         String msg = "";
322                         if (propname != null)
323                         {
324                             msg = String.Format("The Property {0} is required to create a {1} object.", propname, ctorInfo.DeclaringType.FullName);
325                         }
326                         else
327                         {
328                             msg = "propname is null.";
329                         }
330                         throw new XamlParseException(msg, enode as IXmlLineInfo);
331                     }
332                     if (!enode.SkipProperties.Contains(name))
333                         enode.SkipProperties.Add(name);
334                     var value = Context.Values[node];
335                     var serviceProvider = new XamlServiceProvider(enode, Context);
336                     var convertedValue = value?.ConvertTo(parameter?.ParameterType, () => parameter, serviceProvider);
337                     array[i] = convertedValue;
338                 }
339                 return array;
340             }
341
342             return null;
343         }
344
345         static bool IsXaml2009LanguagePrimitive(IElementNode node)
346         {
347             return node.NamespaceURI == XamlParser.X2009Uri;
348         }
349
350         static object CreateLanguagePrimitive(Type nodeType, IElementNode node)
351         {
352             object value = null;
353             if (nodeType == typeof(string))
354                 value = String.Empty;
355             else if (nodeType == typeof(Uri))
356                 value = null;
357             else
358             {
359                 value = Activator.CreateInstance(nodeType);
360                 if (value is Element)
361                 {
362                     if (null != Application.Current)
363                     {
364                         Application.AddResourceChangedCallback(value, (value as Element).OnResourcesChanged);
365                     }
366
367                     if (value is BindableObject)
368                     {
369                         ((BindableObject)value).IsCreateByXaml = true;
370                     }
371                 }
372             }
373
374             if (node.CollectionItems.Count == 1 && node.CollectionItems[0] is ValueNode &&
375                 ((ValueNode)node.CollectionItems[0]).Value is string)
376             {
377                 var valuestring = ((ValueNode)node.CollectionItems[0]).Value as string;
378
379                 if (nodeType == typeof(SByte)) {
380                     sbyte retval;
381                     if (sbyte.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
382                         return retval;
383                 }
384                 if (nodeType == typeof(Int16)) {
385                     short retval;
386                     if (short.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
387                         return retval;
388                 }
389                 if (nodeType == typeof(Int32)) {
390                     int retval;
391                     if (int.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
392                         return retval;
393                 }
394                 if (nodeType == typeof(Int64)) {
395                     long retval;
396                     if (long.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
397                         return retval;
398                 }
399                 if (nodeType == typeof(Byte)) {
400                     byte retval;
401                     if (byte.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
402                         return retval;
403                 }
404                 if (nodeType == typeof(UInt16)) {
405                     ushort retval;
406                     if (ushort.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
407                         return retval;
408                 }
409                 if (nodeType == typeof(UInt32)) {
410                     uint retval;
411                     if (uint.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
412                         return retval;
413                 }
414                 if (nodeType == typeof(UInt64)) {
415                     ulong retval;
416                     if (ulong.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
417                         return retval;
418                 }
419                 if (nodeType == typeof(Single)) {
420                     float retval;
421                     if (float.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
422                         return retval;
423                 }
424                 if (nodeType == typeof(Double)) {
425                     double retval;
426                     if (double.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
427                         return retval;
428                 }
429                 if (nodeType == typeof (Boolean))
430                 {
431                     bool outbool;
432                     if (bool.TryParse(valuestring, out outbool))
433                         return outbool;
434                 }
435                 if (nodeType == typeof(TimeSpan)) {
436                     TimeSpan retval;
437                     if (TimeSpan.TryParse(valuestring, CultureInfo.InvariantCulture, out retval))
438                         return retval;
439                 }
440                 if (nodeType == typeof (char))
441                 {
442                     char retval;
443                     if (char.TryParse(valuestring, out retval))
444                         return retval;
445                 }
446                 if (nodeType == typeof (string))
447                     return valuestring;
448                 if (nodeType == typeof (decimal))
449                 {
450                     decimal retval;
451                     if (decimal.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
452                         return retval;
453                 }
454
455                 else if (nodeType == typeof (Uri))
456                 {
457                     Uri retval;
458                     if (Uri.TryCreate(valuestring, UriKind.RelativeOrAbsolute, out retval))
459                         return retval;
460                 }
461             }
462             return value;
463         }
464     }
465 }