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(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.GetTypeExtensionReference(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));
289 typedef = typeref.ResolveCached();
292 var accordingType = this.GetType().Assembly.GetType(typedef.FullName);
294 if (null != accordingType && accordingType != typeof(Binding.Setter))
296 Context.Values[node] = new EXamlCreateObject(Context, Activator.CreateInstance(accordingType), typeref);
298 else if (null != parameterizedCtorParams)
300 Context.Values[node] = new EXamlCreateObject(Context, null, typeref, parameterizedCtorParams.ToArray());
304 bool canConvertCollectionItem = false;
306 if (!typeref.InheritsFromOrImplements(Context.Module.ImportReference(typeof(List<string>)).Resolve())
308 node.CollectionItems.Count == 1 && (vnode = node.CollectionItems.First() as ValueNode) != null)
310 var valueNode = node.CollectionItems.First() as ValueNode;
312 if (valueNode.CanConvertValue(Context.Module, typeref, (TypeReference)null))
314 var converterType = valueNode.GetConverterType(new ICustomAttributeProvider[] { typeref.Resolve() });
315 if (null != converterType)
317 var converterValue = new EXamlValueConverterFromString(Context, converterType.Resolve(), valueNode.Value as string);
318 Context.Values[node] = new EXamlCreateObject(Context, converterValue, typeref);
322 var valueItem = valueNode.GetBaseValue(Context, typeref);
323 if (null == valueItem)
325 throw new XamlParseException($"Can't convert collection item \"{vnode.Value}\" to object", node);
328 Context.Values[node] = valueItem;
331 canConvertCollectionItem = true;
335 if (false == canConvertCollectionItem)
337 if (!ctorInfo.HasParameters)
339 Context.Values[node] = new EXamlCreateObject(Context, null, typeref);
343 object[] @params = new object[ctorInfo.Parameters.Count];
345 for (int i = 0; i < ctorInfo.Parameters.Count; i++)
347 var param = ctorInfo.Parameters[i];
349 if (ctorInfo.Parameters[i].ParameterType.ResolveCached().IsEnum)
351 @params[i] = NodeILExtensions.GetParsedEnum(Context, param.ParameterType, param.Constant.ToString());
355 @params[i] = param.Constant;
359 Context.Values[node] = new EXamlCreateObject(Context, null, typeref, @params);
364 else if (ctorInfo != null && node.Properties.ContainsKey(XmlName.xArguments) &&
365 !node.Properties.ContainsKey(XmlName.xFactoryMethod) && ctorInfo.MatchXArguments(node, typeref, Module, Context))
367 //IL_0008: ldloca.s 1
369 //IL_000b: call instance void valuetype Test/Foo::'.ctor'(bool)
372 //var ctor = Module.ImportReference(ctorinforef);
373 //Context.IL.Emit(OpCodes.Ldloca, vardef);
374 //Context.IL.Append(PushCtorXArguments(factoryCtorInfo, node));
375 //Context.IL.Emit(OpCodes.Call, ctor);
379 //IL_0000: ldloca.s 0
380 //IL_0002: initobj Test/Foo
382 //Context.IL.Emit(OpCodes.Ldloca, vardef);
383 //Context.IL.Emit(OpCodes.Initobj, Module.ImportReference(typedef));
386 if (typeref.FullName == "Tizen.NUI.Xaml.ArrayExtension")
389 //var visitor = new SetPropertiesVisitor(Context);
390 //foreach (var cnode in node.Properties.Values.ToList())
391 // cnode.Accept(visitor, node);
392 //foreach (var cnode in node.CollectionItems)
393 // cnode.Accept(visitor, node);
395 //markupProvider = new ArrayExtension();
397 //var il = markupProvider.ProvideValue(node, Module, Context, out typeref);
399 //vardef = new VariableDefinition(typeref);
400 //Context.Variables[node] = vardef;
401 //Context.Body.Variables.Add(vardef);
403 //Context.IL.Append(il);
404 //Context.IL.Emit(OpCodes.Stloc, vardef);
406 ////clean the node as it has been fully exhausted
407 //foreach (var prop in node.Properties)
408 // if (!node.SkipProperties.Contains(prop.Key))
409 // node.SkipProperties.Add(prop.Key);
410 //node.CollectionItems.Clear();
417 public void Visit(RootNode node, INode parentNode)
422 var ilnode = (ILRootNode)node;
423 var typeref = ilnode.TypeReference;
424 var vardef = new VariableDefinition(typeref);
425 Context.Variables[node] = vardef;
426 Context.Root = vardef;
427 Context.RootNode = node;
428 //Context.IL.Emit(OpCodes.Ldarg_0);
429 //Context.IL.Emit(OpCodes.Stloc, vardef);
432 public void Visit(ListNode node, INode parentNode)
435 if (EXamlSetPropertiesVisitor.TryGetPropertyName(node, parentNode, out name))
439 bool ValidateCtorArguments(MethodDefinition ctorinfo, ElementNode enode, out string firstMissingProperty)
441 firstMissingProperty = null;
442 foreach (var parameter in ctorinfo.Parameters)
445 parameter.CustomAttributes.First(ca => ca.AttributeType.FullName == "Tizen.NUI.Binding.ParameterAttribute")
446 .ConstructorArguments.First()
448 if (!enode.Properties.ContainsKey(new XmlName("", propname)))
450 firstMissingProperty = propname;
457 List<object> GetCtorXArguments(ElementNode enode, int paramsCount, bool isConstructor)
459 if (!enode.Properties.ContainsKey(XmlName.xArguments))
464 List<object> argumentList = new List<object>();
466 var arguments = new List<INode>();
467 var node = enode.Properties[XmlName.xArguments] as ElementNode;
470 node.Accept(new EXamlSetPropertiesVisitor(Context, true), null);
474 var list = enode.Properties[XmlName.xArguments] as ListNode;
477 foreach (var n in list.CollectionItems)
481 for (int i = 0; i < arguments.Count; i++)
483 argumentList.Add(Context.Values[arguments[i]]);
488 for (int i = arguments.Count; i < paramsCount; i++)
490 argumentList.Add(Type.Missing);
497 static bool IsXaml2009LanguagePrimitive(IElementNode node)
499 if (node.NamespaceURI == XamlParser.X2009Uri)
501 var n = node.XmlType.Name.Split(':')[1];
504 if (node.NamespaceURI != "clr-namespace:System;assembly=mscorlib")
506 var name = node.XmlType.Name.Split(':')[1];
507 if (name == "SByte" ||
521 name == "TimeSpan" ||
527 object GetValueFromLanguagePrimitive(TypeReference typeRef, ElementNode node)
529 var module = Context.Module;
530 var hasValue = node.CollectionItems.Count == 1 && node.CollectionItems[0] is ValueNode &&
531 ((ValueNode)node.CollectionItems[0]).Value is string;
532 var valueString = hasValue ? ((ValueNode)node.CollectionItems[0]).Value as string : string.Empty;
535 TypeDefinition typedef = typeRef.ResolveCached();
537 switch (typedef.FullName)
540 if (hasValue && sbyte.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out sbyte outsbyte))
546 if (hasValue && short.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out short outshort))
552 if (hasValue && int.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out int outint))
558 if (hasValue && long.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out long outlong))
564 if (hasValue && byte.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out byte outbyte))
569 case "System.UInt16":
570 if (hasValue && short.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out short outushort))
575 case "System.UInt32":
576 if (hasValue && uint.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out uint outuint))
581 case "System.UInt64":
582 if (hasValue && long.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out long outulong))
587 case "System.Boolean":
588 if (hasValue && bool.TryParse(valueString, out bool outbool))
593 case "System.String":
596 case "System.Object":
598 module.TypeSystem.Object.ResolveCached()
599 .Methods.FirstOrDefault(md => md.IsConstructor && !md.HasParameters);
600 var ctor = module.ImportReference(ctorinfo);
601 ret = Create(Newobj, ctor);
604 if (hasValue && char.TryParse(valueString, out char outchar))
609 case "System.Decimal":
611 if (hasValue && decimal.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out outdecimal))
620 case "System.Single":
621 if (hasValue && float.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out float outfloat))
626 case "System.Double":
627 if (hasValue && double.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out double outdouble))
632 case "System.TimeSpan":
633 if (hasValue && TimeSpan.TryParse(valueString, CultureInfo.InvariantCulture, out TimeSpan outspan))
644 if (hasValue && Uri.TryCreate(valueString, UriKind.RelativeOrAbsolute, out Uri outuri))
654 ret = new EXamlCreateObject(Context, null, typeRef);
661 List<object> GetCtorArguments(MethodDefinition ctorinfo, ElementNode enode, EXamlContext context)
663 List<object> ret = null;
665 foreach (var parameter in ctorinfo.Parameters)
668 parameter.CustomAttributes.First(ca => ca.AttributeType.FullName == "Tizen.NUI.Binding.ParameterAttribute")
669 .ConstructorArguments.First()
671 var node = enode.Properties[new XmlName("", propname)];
672 if (!enode.SkipProperties.Contains(new XmlName("", propname)))
673 enode.SkipProperties.Add(new XmlName("", propname));
675 if (node is ValueNode valueNode)
677 var valueType = parameter.ParameterType;
679 if ("System.Type" == valueType.FullName)
681 var typeRef = XmlTypeExtensions.GetTypeReference(valueNode.Value as string, Module, node as BaseNode);
682 context.Values[node] = new EXamlCreateObject(context, typeRef);
686 var converterType = valueNode.GetConverterType(new ICustomAttributeProvider[] { parameter, parameter.ParameterType.ResolveCached() });
688 if (null != converterType)
690 var converterValue = new EXamlValueConverterFromString(context, converterType.Resolve(), valueNode.Value as string);
691 context.Values[node] = new EXamlCreateObject(context, converterValue, valueType);
695 context.Values[node] = valueNode.GetBaseValue(context, valueType);
701 ret = new List<object>();
704 ret.Add(context.Values[node]);