2 * Copyright(c) 2022 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 using System.Collections.Generic;
19 using System.Globalization;
26 using static Mono.Cecil.Cil.Instruction;
27 using static Mono.Cecil.Cil.OpCodes;
28 using Tizen.NUI.Xaml.Build.Tasks;
29 using ArrayExtension = Tizen.NUI.Xaml.Build.Tasks.ArrayExtension;
31 namespace Tizen.NUI.EXaml.Build.Tasks
33 class EXamlCreateObjectVisitor : IXamlNodeVisitor
35 public EXamlCreateObjectVisitor(EXamlContext context)
38 Module = context.Module;
41 public EXamlContext Context { get; }
43 ModuleDefinition Module { get; }
45 public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp;
46 public bool StopOnDataTemplate => true;
47 public bool StopOnResourceDictionary => false;
48 public bool VisitNodeOnDataTemplate => false;
49 public bool SkipChildren(INode node, INode parentNode) => false;
51 public bool IsResourceDictionary(ElementNode node)
53 var parentVar = Context.Variables[(IElementNode)node];
54 return parentVar.VariableType.FullName == "Tizen.NUI.Binding.ResourceDictionary"
55 || parentVar.VariableType.Resolve().BaseType?.FullName == "Tizen.NUI.Binding.ResourceDictionary";
58 public void Visit(ValueNode node, INode parentNode)
60 Context.Values[node] = node.Value;
63 public void Visit(MarkupNode node, INode parentNode)
65 //At this point, all MarkupNodes are expanded to ElementNodes
68 public void Visit(ElementNode node, INode parentNode)
70 var typeref = Module.ImportReference(node.XmlType.GetTypeReference(XmlTypeExtensions.ModeOfGetType.Both, Module, node));
72 if (IsXaml2009LanguagePrimitive(node))
74 var vardef = new VariableDefinition(typeref);
75 Context.Variables[node] = vardef;
77 var value = GetValueFromLanguagePrimitive(typeref, node);
79 Context.Values[node] = value;
83 TypeDefinition typedef = typeref.ResolveCached();
85 //if this is a MarkupExtension that can be compiled directly, compile and returns the value
86 var compiledMarkupExtensionName = typeref
87 .GetCustomAttribute(Module, (XamlTask.xamlAssemblyName, XamlTask.xamlNameSpace, "ProvideCompiledAttribute"))
88 ?.ConstructorArguments?[0].Value as string;
89 Type compiledMarkupExtensionType;
90 ICompiledMarkupExtension markupProvider;
91 if (compiledMarkupExtensionName != null &&
92 (compiledMarkupExtensionType = Type.GetType(compiledMarkupExtensionName)) != null &&
93 (markupProvider = Activator.CreateInstance(compiledMarkupExtensionType) as ICompiledMarkupExtension) != null)
96 Context.Values[node] = markupProvider.ProvideValue(node, Module, Context);
98 VariableDefinition vardef = new VariableDefinition(typeref);
99 Context.Variables[node] = vardef;
101 //clean the node as it has been fully exhausted
102 foreach (var prop in node.Properties)
103 if (!node.SkipProperties.Contains(prop.Key))
104 node.SkipProperties.Add(prop.Key);
105 node.CollectionItems.Clear();
109 MethodDefinition factoryCtorInfo = null;
110 MethodDefinition factoryMethodInfo = null;
111 MethodDefinition parameterizedCtorInfo = null;
112 MethodDefinition ctorInfo = null;
114 if (node.Properties.ContainsKey(XmlName.xArguments) && !node.Properties.ContainsKey(XmlName.xFactoryMethod))
116 factoryCtorInfo = typedef.AllMethods().FirstOrDefault(md => md.IsConstructor &&
119 md.MatchXArguments(node, typeref, Module, Context));
120 if (factoryCtorInfo == null)
122 throw new XamlParseException(
123 string.Format("No constructors found for {0} with matching x:Arguments", typedef.FullName), node);
125 ctorInfo = factoryCtorInfo;
126 if (!typedef.IsValueType) //for ctor'ing typedefs, we first have to ldloca before the params
128 VariableDefinition vardef = new VariableDefinition(typeref);
129 Context.Variables[node] = vardef;
131 var argumentList = GetCtorXArguments(node, factoryCtorInfo.Parameters.Count, true);
132 Context.Values[node] = new EXamlCreateObject(Context, null, typedef, argumentList.ToArray());
136 else if (node.Properties.ContainsKey(XmlName.xFactoryMethod))
138 var factoryMethod = (string)(node.Properties[XmlName.xFactoryMethod] as ValueNode).Value;
139 factoryMethodInfo = typedef.AllMethods().FirstOrDefault(md => !md.IsConstructor &&
140 md.Name == factoryMethod &&
142 md.MatchXArguments(node, typeref, Module, Context));
144 if (factoryMethodInfo == null)
146 var typeExtensionRef = Module.ImportReference(node.XmlType.GetTypeReference(XmlTypeExtensions.ModeOfGetType.OnlyGetTypeExtension, Module, node));
147 typeExtensionRef = typeExtensionRef?.ResolveCached();
149 if (null != typeExtensionRef)
151 factoryMethodInfo = typeExtensionRef.ResolveCached().AllMethods().FirstOrDefault(md => !md.IsConstructor &&
152 md.Name == factoryMethod &&
154 md.MatchXArguments(node, typeExtensionRef, Module, Context));
158 if (factoryMethodInfo == null)
160 throw new XamlParseException(
161 String.Format("No static method found for {0}::{1} ({2})", typedef.FullName, factoryMethod, null), node);
164 VariableDefinition vardef = new VariableDefinition(typeref);
165 Context.Variables[node] = vardef;
167 var argumentList = GetCtorXArguments(node, factoryMethodInfo.Parameters.Count, false);
168 Context.Values[node] = new EXamlCreateObject(Context, null, typedef, factoryMethodInfo, argumentList?.ToArray());
172 if (ctorInfo == null && factoryMethodInfo == null)
174 parameterizedCtorInfo = typedef.Methods.FirstOrDefault(md => md.IsConstructor &&
179 pd.CustomAttributes.Any(
181 ca.AttributeType.FullName ==
182 "Tizen.NUI.Binding.ParameterAttribute")));
184 string missingCtorParameter = null;
185 List<object> parameterizedCtorParams = null;
187 if (parameterizedCtorInfo != null && ValidateCtorArguments(parameterizedCtorInfo, node, out missingCtorParameter))
189 ctorInfo = parameterizedCtorInfo;
190 parameterizedCtorParams = GetCtorArguments(parameterizedCtorInfo, node, Context);
192 //IL_0000: ldstr "foo"
193 //Context.IL.Append(PushCtorArguments(parameterizedCtorInfo, node));
196 ctorInfo = ctorInfo ?? typedef.Methods.FirstOrDefault(md => md.IsConstructor && !md.HasParameters && !md.IsStatic);
198 if (null == ctorInfo)
200 foreach (var method in typedef.Methods)
202 if (method.IsConstructor && !method.IsStatic)
204 bool areAllParamsDefault = true;
206 foreach (var param in method.Parameters)
208 if (!param.HasDefault)
210 areAllParamsDefault = false;
215 if (areAllParamsDefault)
217 if (null == ctorInfo)
223 throw new XamlParseException($"{typedef.FullName} has more than one constructor which params are all default.", node);
229 if (null == ctorInfo && !typedef.IsValueType)
231 throw new XamlParseException($"{typedef.FullName} has no constructor which params are all default.", node);
235 if (parameterizedCtorInfo != null && ctorInfo == null)
236 //there was a parameterized ctor, we didn't use it
237 throw new XamlParseException($"The Property '{missingCtorParameter}' is required to create a '{typedef.FullName}' object.", node);
238 var ctorinforef = ctorInfo?.ResolveGenericParameters(typeref, Module);
240 var factorymethodinforef = factoryMethodInfo?.ResolveGenericParameters(typeref, Module);
241 var implicitOperatorref = typedef.Methods.FirstOrDefault(md =>
245 md.Name == "op_Implicit" && md.Parameters[0].ParameterType.FullName == "System.String");
247 if (ctorinforef != null || factorymethodinforef != null || typedef.IsValueType)
249 VariableDefinition vardef = new VariableDefinition(typeref);
250 Context.Variables[node] = vardef;
252 ValueNode vnode = null;
253 if (node.CollectionItems.Count == 1 && (vnode = node.CollectionItems.First() as ValueNode) != null &&
254 vardef.VariableType.IsValueType)
256 Context.Values[node] = vnode.GetBaseValue(Context, typeref);
258 else if (node.CollectionItems.Count == 1 && (vnode = node.CollectionItems.First() as ValueNode) != null &&
259 implicitOperatorref != null)
261 var converterType = vnode.GetConverterType(new ICustomAttributeProvider[] { typeref.ResolveCached() });
262 if (null == converterType)
264 var realValue = vnode.GetBaseValue(Context, typeref);
265 Context.Values[node] = new EXamlCreateObject(Context, realValue, typeref);
269 var converterValue = new EXamlValueConverterFromString(Context, converterType.Resolve(), vnode.Value as string);
270 Context.Values[node] = new EXamlCreateObject(Context, converterValue, typeref);
273 else if (factorymethodinforef != null)
276 //Context.IL.Emit(OpCodes.Call, Module.ImportReference(factorymethodinforef));
277 //Context.IL.Emit(OpCodes.Stloc, vardef);
279 else if (!typedef.IsValueType)
281 var ctor = Module.ImportReference(ctorinforef);
282 //IL_0001: newobj instance void class [Tizen.NUI.Xaml.UIComponents]Tizen.NUI.Xaml.UIComponents.Button::'.ctor'()
284 //Context.IL.Emit(OpCodes.Newobj, ctor);
285 //Context.IL.Emit(OpCodes.Stloc, vardef);
286 if (typeref.FullName == "Tizen.NUI.Xaml.ArrayExtension")
288 typeref = Module.ImportReference(typeof(ArrayExtension));
291 var accordingType = this.GetType().Assembly.GetType(typeref.FullName);
293 if (null != accordingType && accordingType != typeof(Binding.Setter))
295 Context.Values[node] = new EXamlCreateObject(Context, Activator.CreateInstance(accordingType), typeref);
297 else if (null != parameterizedCtorParams)
299 Context.Values[node] = new EXamlCreateObject(Context, null, typeref, parameterizedCtorParams.ToArray());
303 bool canConvertCollectionItem = false;
305 if (!typeref.InheritsFromOrImplements(Context.Module.ImportReference(typeof(List<string>)).Resolve())
307 node.CollectionItems.Count == 1 && (vnode = node.CollectionItems.First() as ValueNode) != null)
309 var valueNode = node.CollectionItems.First() as ValueNode;
311 if (valueNode.CanConvertValue(Context.Module, typeref, (TypeReference)null))
313 var converterType = valueNode.GetConverterType(new ICustomAttributeProvider[] { typeref.Resolve() });
314 if (null != converterType)
316 var converterValue = new EXamlValueConverterFromString(Context, converterType.Resolve(), valueNode.Value as string);
317 Context.Values[node] = new EXamlCreateObject(Context, converterValue, typeref);
321 var valueItem = valueNode.GetBaseValue(Context, typeref);
322 if (null == valueItem)
324 throw new XamlParseException($"Can't convert collection item \"{vnode.Value}\" to object", node);
327 Context.Values[node] = valueItem;
330 canConvertCollectionItem = true;
334 if (false == canConvertCollectionItem)
336 if (!ctorInfo.HasParameters)
338 Context.Values[node] = new EXamlCreateObject(Context, null, typeref);
342 object[] @params = new object[ctorInfo.Parameters.Count];
344 for (int i = 0; i < ctorInfo.Parameters.Count; i++)
346 var param = ctorInfo.Parameters[i];
348 if (ctorInfo.Parameters[i].ParameterType.ResolveCached().IsEnum)
350 @params[i] = NodeILExtensions.GetParsedEnum(Context, param.ParameterType, param.Constant.ToString());
354 @params[i] = param.Constant;
358 Context.Values[node] = new EXamlCreateObject(Context, null, typeref, @params);
363 else if (ctorInfo != null && node.Properties.ContainsKey(XmlName.xArguments) &&
364 !node.Properties.ContainsKey(XmlName.xFactoryMethod) && ctorInfo.MatchXArguments(node, typeref, Module, Context))
366 //IL_0008: ldloca.s 1
368 //IL_000b: call instance void valuetype Test/Foo::'.ctor'(bool)
371 //var ctor = Module.ImportReference(ctorinforef);
372 //Context.IL.Emit(OpCodes.Ldloca, vardef);
373 //Context.IL.Append(PushCtorXArguments(factoryCtorInfo, node));
374 //Context.IL.Emit(OpCodes.Call, ctor);
378 //IL_0000: ldloca.s 0
379 //IL_0002: initobj Test/Foo
381 //Context.IL.Emit(OpCodes.Ldloca, vardef);
382 //Context.IL.Emit(OpCodes.Initobj, Module.ImportReference(typedef));
385 if (typeref.FullName == "Tizen.NUI.Xaml.ArrayExtension")
388 //var visitor = new SetPropertiesVisitor(Context);
389 //foreach (var cnode in node.Properties.Values.ToList())
390 // cnode.Accept(visitor, node);
391 //foreach (var cnode in node.CollectionItems)
392 // cnode.Accept(visitor, node);
394 //markupProvider = new ArrayExtension();
396 //var il = markupProvider.ProvideValue(node, Module, Context, out typeref);
398 //vardef = new VariableDefinition(typeref);
399 //Context.Variables[node] = vardef;
400 //Context.Body.Variables.Add(vardef);
402 //Context.IL.Append(il);
403 //Context.IL.Emit(OpCodes.Stloc, vardef);
405 ////clean the node as it has been fully exhausted
406 //foreach (var prop in node.Properties)
407 // if (!node.SkipProperties.Contains(prop.Key))
408 // node.SkipProperties.Add(prop.Key);
409 //node.CollectionItems.Clear();
416 public void Visit(RootNode node, INode parentNode)
421 var ilnode = (ILRootNode)node;
422 var typeref = ilnode.TypeReference;
423 var vardef = new VariableDefinition(typeref);
424 Context.Variables[node] = vardef;
425 Context.Root = vardef;
426 Context.RootNode = node;
427 //Context.IL.Emit(OpCodes.Ldarg_0);
428 //Context.IL.Emit(OpCodes.Stloc, vardef);
431 public void Visit(ListNode node, INode parentNode)
434 if (EXamlSetPropertiesVisitor.TryGetPropertyName(node, parentNode, out name))
438 bool ValidateCtorArguments(MethodDefinition ctorinfo, ElementNode enode, out string firstMissingProperty)
440 firstMissingProperty = null;
441 foreach (var parameter in ctorinfo.Parameters)
444 parameter.CustomAttributes.First(ca => ca.AttributeType.FullName == "Tizen.NUI.Binding.ParameterAttribute")
445 .ConstructorArguments.First()
447 if (!enode.Properties.ContainsKey(new XmlName("", propname)))
449 firstMissingProperty = propname;
456 List<object> GetCtorXArguments(ElementNode enode, int paramsCount, bool isConstructor)
458 if (!enode.Properties.ContainsKey(XmlName.xArguments))
463 List<object> argumentList = new List<object>();
465 var arguments = new List<INode>();
466 var node = enode.Properties[XmlName.xArguments] as ElementNode;
469 node.Accept(new EXamlSetPropertiesVisitor(Context, true), null);
473 var list = enode.Properties[XmlName.xArguments] as ListNode;
476 foreach (var n in list.CollectionItems)
480 for (int i = 0; i < arguments.Count; i++)
482 argumentList.Add(Context.Values[arguments[i]]);
487 for (int i = arguments.Count; i < paramsCount; i++)
489 argumentList.Add(Type.Missing);
496 static bool IsXaml2009LanguagePrimitive(IElementNode node)
498 if (node.NamespaceURI == XamlParser.X2009Uri)
500 var n = node.XmlType.Name.Split(':')[1];
503 if (node.NamespaceURI != "clr-namespace:System;assembly=mscorlib")
505 var name = node.XmlType.Name.Split(':')[1];
506 if (name == "SByte" ||
520 name == "TimeSpan" ||
526 object GetValueFromLanguagePrimitive(TypeReference typeRef, ElementNode node)
528 var module = Context.Module;
529 var hasValue = node.CollectionItems.Count == 1 && node.CollectionItems[0] is ValueNode &&
530 ((ValueNode)node.CollectionItems[0]).Value is string;
531 var valueString = hasValue ? ((ValueNode)node.CollectionItems[0]).Value as string : string.Empty;
534 TypeDefinition typedef = typeRef.ResolveCached();
536 switch (typedef.FullName)
539 if (hasValue && sbyte.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out sbyte outsbyte))
545 if (hasValue && short.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out short outshort))
551 if (hasValue && int.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out int outint))
557 if (hasValue && long.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out long outlong))
563 if (hasValue && byte.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out byte outbyte))
568 case "System.UInt16":
569 if (hasValue && short.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out short outushort))
574 case "System.UInt32":
575 if (hasValue && uint.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out uint outuint))
580 case "System.UInt64":
581 if (hasValue && long.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out long outulong))
586 case "System.Boolean":
587 if (hasValue && bool.TryParse(valueString, out bool outbool))
592 case "System.String":
595 case "System.Object":
597 module.TypeSystem.Object.ResolveCached()
598 .Methods.FirstOrDefault(md => md.IsConstructor && !md.HasParameters);
599 var ctor = module.ImportReference(ctorinfo);
600 ret = Create(Newobj, ctor);
603 if (hasValue && char.TryParse(valueString, out char outchar))
608 case "System.Decimal":
610 if (hasValue && decimal.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out outdecimal))
619 case "System.Single":
620 if (hasValue && float.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out float outfloat))
625 case "System.Double":
626 if (hasValue && double.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out double outdouble))
631 case "System.TimeSpan":
632 if (hasValue && TimeSpan.TryParse(valueString, CultureInfo.InvariantCulture, out TimeSpan outspan))
642 if (hasValue && Uri.TryCreate(valueString, UriKind.RelativeOrAbsolute, out Uri outuri))
652 ret = new EXamlCreateObject(Context, null, typeRef);
659 List<object> GetCtorArguments(MethodDefinition ctorinfo, ElementNode enode, EXamlContext context)
661 List<object> ret = null;
663 foreach (var parameter in ctorinfo.Parameters)
666 parameter.CustomAttributes.First(ca => ca.AttributeType.FullName == "Tizen.NUI.Binding.ParameterAttribute")
667 .ConstructorArguments.First()
669 var node = enode.Properties[new XmlName("", propname)];
670 if (!enode.SkipProperties.Contains(new XmlName("", propname)))
671 enode.SkipProperties.Add(new XmlName("", propname));
673 if (node is ValueNode valueNode)
675 var valueType = parameter.ParameterType;
677 if ("System.Type" == valueType.FullName)
679 var typeRef = XmlTypeExtensions.GetTypeReference(valueNode.Value as string, Module, node as BaseNode, XmlTypeExtensions.ModeOfGetType.Both);
680 context.Values[node] = new EXamlCreateObject(context, typeRef);
684 var converterType = valueNode.GetConverterType(new ICustomAttributeProvider[] { parameter, parameter.ParameterType.ResolveCached() });
686 if (null != converterType)
688 var converterValue = new EXamlValueConverterFromString(context, converterType.Resolve(), valueNode.Value as string);
689 context.Values[node] = new EXamlCreateObject(context, converterValue, valueType);
693 context.Values[node] = valueNode.GetBaseValue(context, valueType);
699 ret = new List<object>();
702 ret.Add(context.Values[node]);