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;
30 using Tizen.NUI.Binding;
32 namespace Tizen.NUI.EXaml.Build.Tasks
34 class EXamlCreateObjectVisitor : IXamlNodeVisitor
36 public EXamlCreateObjectVisitor(EXamlContext context)
39 Module = context.Module;
42 public EXamlContext Context { get; }
44 ModuleDefinition Module { get; }
46 public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp;
47 public bool StopOnDataTemplate => true;
48 public bool StopOnResourceDictionary => false;
49 public bool VisitNodeOnDataTemplate => false;
50 public bool SkipChildren(INode node, INode parentNode) => false;
52 public bool IsResourceDictionary(ElementNode node)
54 var parentVar = Context.Variables[(IElementNode)node];
55 return parentVar.VariableType.FullName == "Tizen.NUI.Binding.ResourceDictionary"
56 || parentVar.VariableType.Resolve().BaseType?.FullName == "Tizen.NUI.Binding.ResourceDictionary";
59 public void Visit(ValueNode node, INode parentNode)
61 Context.Values[node] = node.Value;
64 public void Visit(MarkupNode node, INode parentNode)
66 //At this point, all MarkupNodes are expanded to ElementNodes
69 public void Visit(ElementNode node, INode parentNode)
71 var typeref = Module.ImportReference(node.XmlType.GetTypeReference(XmlTypeExtensions.ModeOfGetType.Both, Module, node));
73 if (IsXaml2009LanguagePrimitive(node))
75 var vardef = new VariableDefinition(typeref);
76 Context.Variables[node] = vardef;
78 var value = GetValueFromLanguagePrimitive(typeref, node);
80 Context.Values[node] = value;
84 TypeDefinition typedef = typeref.ResolveCached();
86 //if this is a MarkupExtension that can be compiled directly, compile and returns the value
87 var compiledMarkupExtensionName = typeref
88 .GetCustomAttribute(Module, (XamlTask.xamlAssemblyName, XamlTask.xamlNameSpace, "ProvideCompiledAttribute"))
89 ?.ConstructorArguments?[0].Value as string;
90 Type compiledMarkupExtensionType;
91 ICompiledMarkupExtension markupProvider;
92 if (compiledMarkupExtensionName != null &&
93 (compiledMarkupExtensionType = Type.GetType(compiledMarkupExtensionName)) != null &&
94 (markupProvider = Activator.CreateInstance(compiledMarkupExtensionType) as ICompiledMarkupExtension) != null)
97 Context.Values[node] = markupProvider.ProvideValue(node, Module, Context);
99 VariableDefinition vardef = new VariableDefinition(typeref);
100 Context.Variables[node] = vardef;
102 //clean the node as it has been fully exhausted
103 foreach (var prop in node.Properties)
104 if (!node.SkipProperties.Contains(prop.Key))
105 node.SkipProperties.Add(prop.Key);
106 node.CollectionItems.Clear();
110 MethodDefinition factoryCtorInfo = null;
111 MethodDefinition factoryMethodInfo = null;
112 MethodDefinition parameterizedCtorInfo = null;
113 MethodDefinition ctorInfo = null;
115 if (node.Properties.ContainsKey(XmlName.xArguments) && !node.Properties.ContainsKey(XmlName.xFactoryMethod))
117 factoryCtorInfo = typedef.AllMethods().FirstOrDefault(md => md.IsConstructor &&
120 md.MatchXArguments(node, typeref, Module, Context));
121 if (factoryCtorInfo == null)
123 throw new XamlParseException(
124 string.Format("No constructors found for {0} with matching x:Arguments", typedef.FullName), node);
126 ctorInfo = factoryCtorInfo;
127 if (!typedef.IsValueType) //for ctor'ing typedefs, we first have to ldloca before the params
129 VariableDefinition vardef = new VariableDefinition(typeref);
130 Context.Variables[node] = vardef;
132 var argumentList = GetCtorXArguments(node, factoryCtorInfo.Parameters.Count, true);
133 Context.Values[node] = new EXamlCreateObject(Context, null, typedef, argumentList.ToArray());
137 else if (node.Properties.ContainsKey(XmlName.xFactoryMethod))
139 var factoryMethod = (string)(node.Properties[XmlName.xFactoryMethod] as ValueNode).Value;
140 factoryMethodInfo = typedef.AllMethods().FirstOrDefault(md => !md.IsConstructor &&
141 md.Name == factoryMethod &&
143 md.MatchXArguments(node, typeref, Module, Context));
145 if (factoryMethodInfo == null)
147 var typeExtensionRef = Module.ImportReference(node.XmlType.GetTypeReference(XmlTypeExtensions.ModeOfGetType.OnlyGetTypeExtension, Module, node));
148 typeExtensionRef = typeExtensionRef?.ResolveCached();
150 if (null != typeExtensionRef)
152 factoryMethodInfo = typeExtensionRef.ResolveCached().AllMethods().FirstOrDefault(md => !md.IsConstructor &&
153 md.Name == factoryMethod &&
155 md.MatchXArguments(node, typeExtensionRef, Module, Context));
159 if (factoryMethodInfo == null)
161 throw new XamlParseException(
162 String.Format("No static method found for {0}::{1} ({2})", typedef.FullName, factoryMethod, null), node);
165 VariableDefinition vardef = new VariableDefinition(typeref);
166 Context.Variables[node] = vardef;
168 var argumentList = GetCtorXArguments(node, factoryMethodInfo.Parameters.Count, false);
169 Context.Values[node] = new EXamlCreateObject(Context, null, typedef, factoryMethodInfo, argumentList?.ToArray());
173 if (ctorInfo == null && factoryMethodInfo == null)
175 parameterizedCtorInfo = typedef.Methods.FirstOrDefault(md => md.IsConstructor &&
180 pd.CustomAttributes.Any(
182 ca.AttributeType.FullName ==
183 "Tizen.NUI.Binding.ParameterAttribute")));
185 string missingCtorParameter = null;
186 List<object> parameterizedCtorParams = null;
188 if (parameterizedCtorInfo != null && ValidateCtorArguments(parameterizedCtorInfo, node, out missingCtorParameter))
190 ctorInfo = parameterizedCtorInfo;
191 parameterizedCtorParams = GetCtorArguments(parameterizedCtorInfo, node, Context);
193 //IL_0000: ldstr "foo"
194 //Context.IL.Append(PushCtorArguments(parameterizedCtorInfo, node));
197 ctorInfo = ctorInfo ?? typedef.Methods.FirstOrDefault(md => md.IsConstructor && !md.HasParameters && !md.IsStatic);
199 if (null == ctorInfo)
201 foreach (var method in typedef.Methods)
203 if (method.IsConstructor && !method.IsStatic)
205 bool areAllParamsDefault = true;
207 foreach (var param in method.Parameters)
209 if (!param.HasDefault)
211 areAllParamsDefault = false;
216 if (areAllParamsDefault)
218 if (null == ctorInfo)
224 throw new XamlParseException($"{typedef.FullName} has more than one constructor which params are all default.", node);
230 if (null == ctorInfo && !typedef.IsValueType)
232 throw new XamlParseException($"{typedef.FullName} has no constructor which params are all default.", node);
236 if (parameterizedCtorInfo != null && ctorInfo == null)
237 //there was a parameterized ctor, we didn't use it
238 throw new XamlParseException($"The Property '{missingCtorParameter}' is required to create a '{typedef.FullName}' object.", node);
239 var ctorinforef = ctorInfo?.ResolveGenericParameters(typeref, Module);
241 var factorymethodinforef = factoryMethodInfo?.ResolveGenericParameters(typeref, Module);
242 var implicitOperatorref = typedef.Methods.FirstOrDefault(md =>
246 md.Name == "op_Implicit" && md.Parameters[0].ParameterType.FullName == "System.String");
248 if (ctorinforef != null || factorymethodinforef != null || typedef.IsValueType)
250 VariableDefinition vardef = new VariableDefinition(typeref);
251 Context.Variables[node] = vardef;
253 ValueNode vnode = null;
254 if (node.CollectionItems.Count == 1 && (vnode = node.CollectionItems.First() as ValueNode) != null &&
255 vardef.VariableType.IsValueType)
257 Context.Values[node] = vnode.GetBaseValue(Context, typeref);
259 else if (node.CollectionItems.Count == 1 && (vnode = node.CollectionItems.First() as ValueNode) != null &&
260 implicitOperatorref != null)
262 var converterType = vnode.GetConverterType(new ICustomAttributeProvider[] { typeref.ResolveCached() });
263 if (null == converterType)
265 var realValue = vnode.GetBaseValue(Context, typeref);
266 Context.Values[node] = new EXamlCreateObject(Context, realValue, typeref);
270 var converterValue = new EXamlValueConverterFromString(Context, converterType.Resolve(), vnode.Value as string);
271 Context.Values[node] = new EXamlCreateObject(Context, converterValue, typeref);
274 else if (factorymethodinforef != null)
277 //Context.IL.Emit(OpCodes.Call, Module.ImportReference(factorymethodinforef));
278 //Context.IL.Emit(OpCodes.Stloc, vardef);
280 else if (!typedef.IsValueType)
282 var ctor = Module.ImportReference(ctorinforef);
283 //IL_0001: newobj instance void class [Tizen.NUI.Xaml.UIComponents]Tizen.NUI.Xaml.UIComponents.Button::'.ctor'()
285 //Context.IL.Emit(OpCodes.Newobj, ctor);
286 //Context.IL.Emit(OpCodes.Stloc, vardef);
287 if (typeref.FullName == "Tizen.NUI.Xaml.ArrayExtension")
289 typeref = Module.ImportReference(typeof(ArrayExtension));
292 var accordingType = this.GetType().Assembly.GetType(typeref.FullName);
294 if (null != accordingType && accordingType != typeof(Binding.Setter) && accordingType != typeof(ResourceDictionary))
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 var argumentList = GetCtorXArguments(node, factoryCtorInfo.Parameters.Count, true);
368 Context.Values[node] = new EXamlCreateObject(Context, null, typedef, argumentList.ToArray());
373 Context.Values[node] = new EXamlCreateObject(Context, null, typedef, null);
377 if (typeref.FullName == "Tizen.NUI.Xaml.ArrayExtension")
380 //var visitor = new SetPropertiesVisitor(Context);
381 //foreach (var cnode in node.Properties.Values.ToList())
382 // cnode.Accept(visitor, node);
383 //foreach (var cnode in node.CollectionItems)
384 // cnode.Accept(visitor, node);
386 //markupProvider = new ArrayExtension();
388 //var il = markupProvider.ProvideValue(node, Module, Context, out typeref);
390 //vardef = new VariableDefinition(typeref);
391 //Context.Variables[node] = vardef;
392 //Context.Body.Variables.Add(vardef);
394 //Context.IL.Append(il);
395 //Context.IL.Emit(OpCodes.Stloc, vardef);
397 ////clean the node as it has been fully exhausted
398 //foreach (var prop in node.Properties)
399 // if (!node.SkipProperties.Contains(prop.Key))
400 // node.SkipProperties.Add(prop.Key);
401 //node.CollectionItems.Clear();
408 public void Visit(RootNode node, INode parentNode)
413 var ilnode = (ILRootNode)node;
414 var typeref = ilnode.TypeReference;
415 var vardef = new VariableDefinition(typeref);
416 Context.Variables[node] = vardef;
417 Context.Root = vardef;
418 Context.RootNode = node;
419 //Context.IL.Emit(OpCodes.Ldarg_0);
420 //Context.IL.Emit(OpCodes.Stloc, vardef);
423 public void Visit(ListNode node, INode parentNode)
426 if (EXamlSetPropertiesVisitor.TryGetPropertyName(node, parentNode, out name))
430 bool ValidateCtorArguments(MethodDefinition ctorinfo, ElementNode enode, out string firstMissingProperty)
432 firstMissingProperty = null;
433 foreach (var parameter in ctorinfo.Parameters)
436 parameter.CustomAttributes.First(ca => ca.AttributeType.FullName == "Tizen.NUI.Binding.ParameterAttribute")
437 .ConstructorArguments.First()
439 if (!enode.Properties.ContainsKey(new XmlName("", propname)))
441 firstMissingProperty = propname;
448 List<object> GetCtorXArguments(ElementNode enode, int paramsCount, bool isConstructor)
450 if (!enode.Properties.ContainsKey(XmlName.xArguments))
455 List<object> argumentList = new List<object>();
457 var arguments = new List<INode>();
458 var node = enode.Properties[XmlName.xArguments] as ElementNode;
461 node.Accept(new EXamlSetPropertiesVisitor(Context, true), null);
465 var list = enode.Properties[XmlName.xArguments] as ListNode;
468 foreach (var n in list.CollectionItems)
472 for (int i = 0; i < arguments.Count; i++)
474 argumentList.Add(Context.Values[arguments[i]]);
479 for (int i = arguments.Count; i < paramsCount; i++)
481 argumentList.Add(Type.Missing);
488 static bool IsXaml2009LanguagePrimitive(IElementNode node)
490 if (node.NamespaceURI == XamlParser.X2009Uri)
492 var n = node.XmlType.Name.Split(':')[1];
493 return n != "Array" && n != "DateTime";
495 if (node.NamespaceURI != "clr-namespace:System;assembly=mscorlib")
497 var name = node.XmlType.Name.Split(':')[1];
498 if (name == "SByte" ||
512 name == "TimeSpan" ||
518 object GetValueFromLanguagePrimitive(TypeReference typeRef, ElementNode node)
520 var module = Context.Module;
521 var hasValue = node.CollectionItems.Count == 1 && node.CollectionItems[0] is ValueNode &&
522 ((ValueNode)node.CollectionItems[0]).Value is string;
523 var valueString = hasValue ? ((ValueNode)node.CollectionItems[0]).Value as string : string.Empty;
526 TypeDefinition typedef = typeRef.ResolveCached();
528 switch (typedef.FullName)
531 if (hasValue && sbyte.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out sbyte outsbyte))
537 if (hasValue && short.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out short outshort))
543 if (hasValue && int.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out int outint))
549 if (hasValue && long.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out long outlong))
555 if (hasValue && byte.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out byte outbyte))
560 case "System.UInt16":
561 if (hasValue && short.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out short outushort))
566 case "System.UInt32":
567 if (hasValue && uint.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out uint outuint))
572 case "System.UInt64":
573 if (hasValue && long.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out long outulong))
578 case "System.Boolean":
579 if (hasValue && bool.TryParse(valueString, out bool outbool))
584 case "System.String":
587 case "System.Object":
589 module.TypeSystem.Object.ResolveCached()
590 .Methods.FirstOrDefault(md => md.IsConstructor && !md.HasParameters);
591 var ctor = module.ImportReference(ctorinfo);
592 ret = Create(Newobj, ctor);
595 if (hasValue && char.TryParse(valueString, out char outchar))
600 case "System.Decimal":
602 if (hasValue && decimal.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out outdecimal))
611 case "System.Single":
612 if (hasValue && float.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out float outfloat))
617 case "System.Double":
618 if (hasValue && double.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out double outdouble))
623 case "System.TimeSpan":
624 if (hasValue && TimeSpan.TryParse(valueString, CultureInfo.InvariantCulture, out TimeSpan outspan))
634 if (hasValue && Uri.TryCreate(valueString, UriKind.RelativeOrAbsolute, out Uri outuri))
644 ret = new EXamlCreateObject(Context, null, typeRef);
651 List<object> GetCtorArguments(MethodDefinition ctorinfo, ElementNode enode, EXamlContext context)
653 List<object> ret = null;
655 foreach (var parameter in ctorinfo.Parameters)
658 parameter.CustomAttributes.First(ca => ca.AttributeType.FullName == "Tizen.NUI.Binding.ParameterAttribute")
659 .ConstructorArguments.First()
661 var node = enode.Properties[new XmlName("", propname)];
662 if (!enode.SkipProperties.Contains(new XmlName("", propname)))
663 enode.SkipProperties.Add(new XmlName("", propname));
665 if (node is ValueNode valueNode)
667 var valueType = parameter.ParameterType;
669 if ("System.Type" == valueType.FullName)
671 var typeRef = XmlTypeExtensions.GetTypeReference(valueNode.Value as string, Module, node as BaseNode, XmlTypeExtensions.ModeOfGetType.Both);
672 context.Values[node] = new EXamlCreateObject(context, typeRef);
676 var converterType = valueNode.GetConverterType(new ICustomAttributeProvider[] { parameter, parameter.ParameterType.ResolveCached() });
678 if (null != converterType)
680 var converterValue = new EXamlValueConverterFromString(context, converterType.Resolve(), valueNode.Value as string);
681 context.Values[node] = new EXamlCreateObject(context, converterValue, valueType);
685 context.Values[node] = valueNode.GetBaseValue(context, valueType);
691 ret = new List<object>();
694 ret.Add(context.Values[node]);