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