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