/// <summary> /// Binds the table element property to the property expression stack. /// </summary> /// <param name="propertyInfo">The property information.</param> /// <param name="context">The page builder context.</param> /// <param name="variableList">The variable list.</param> /// <param name="propertyExpressions">The property expressions.</param> /// <param name="attribute">The attribute.</param> /// <param name="customAttributes">The custom attributes.</param> private void BindTableElementProperty( PropertyInfo propertyInfo, PageBuilderContext context, ICollection <ParameterExpression> variableList, List <Expression> propertyExpressions, ElementLocatorAttribute attribute, IEnumerable <object> customAttributes) { // Get any types and methods var propertyType = propertyInfo.PropertyType; var cellType = propertyType.GetGenericArguments().First(); var enumeratorType = typeof(IEnumerable <>).MakeGenericType(cellType); var assignItemInfo = propertyType.GetMethod("SetDriver", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic); // Create any variables and other experssions var driverVariable = Expression.Variable(enumeratorType, "driver"); variableList.Add(driverVariable); var driverExpression = this.CreateElementListItem(context, this.GetTableDriverType(), variableList, propertyExpressions, attribute, customAttributes); // Add any necessary property expressions var itemVariable = context.CurrentElement.Expression; propertyExpressions.Add(Expression.Assign(itemVariable, Expression.Property(context.Document.Expression, propertyInfo))); propertyExpressions.Add(Expression.IfThen( Expression.Equal(itemVariable, Expression.Constant(null, propertyType)), Expression.Assign(itemVariable, Expression.New(propertyType)))); propertyExpressions.Add(Expression.Assign(driverVariable, Expression.Convert(driverExpression, enumeratorType))); propertyExpressions.Add(Expression.Call(itemVariable, assignItemInfo, new Expression[] { driverVariable })); }
public void TestPageBuilderContextWhenConstructedReturnsTheConstructedValues() { var browser = new ExpressionData(null, typeof(object)); var document = new ExpressionData(null, typeof(object)); var parentElement = new ExpressionData(null, typeof(object)); var context = new PageBuilderContext(browser, parentElement, document); Assert.AreSame(browser, context.Browser); Assert.AreSame(document, context.Document); Assert.AreSame(parentElement, context.ParentElement); Assert.IsNull(context.RootLocator); Assert.IsNull(context.CurrentElement); }
/// <summary> /// Creates the new item expression that creates the object and initial mapping. /// </summary> /// <param name="elementType">Type of the element.</param> /// <returns>The initial creation lambda expression.</returns> /// <exception cref="System.InvalidOperationException">Thrown if the constructor is invalid.</exception> private Expression <Func <TParent, IBrowser, Action <TOutput>, TOutput> > CreateNewItemExpression(Type elementType) { var browserParameter = Expression.Parameter(typeof(IBrowser), "browser"); var browserArgument = new ExpressionData(browserParameter, typeof(IBrowser), "browser"); var parentParameter = Expression.Parameter(typeof(TParent), "parent"); var parentArgument = new ExpressionData(parentParameter, typeof(TParent), "rootContext"); var docVariable = Expression.Variable(elementType); var documentData = new ExpressionData(docVariable, elementType, elementType.Name); var context = new PageBuilderContext(browserArgument, parentArgument, documentData); var constructor = this.GetConstructor(elementType, context); if (constructor == null) { throw this.CreateConstructorException(null, elementType); } var actionParameter = Expression.Parameter(typeof(Action <TOutput>), "action"); //Spin though properties and make an initializer for anything we can set that has an attribute var pageMethodInfo = new Action <TOutput, Action <TOutput> >(this.AssignPageAttributes).GetMethodInfo(); var expressions = new List <Expression> { Expression.Assign(docVariable, Expression.New(constructor.Item1, constructor.Item2)), Expression.Call( Expression.Constant(this), pageMethodInfo, Expression.Convert(docVariable, typeof(TOutput)), actionParameter) }; this.MapObjectProperties(expressions, elementType, context); expressions.Add(docVariable); var methodCall = Expression.Block(new[] { docVariable }, expressions); return(Expression.Lambda <Func <TParent, IBrowser, Action <TOutput>, TOutput> >(methodCall, parentParameter, browserParameter, actionParameter)); }
/// <summary> /// Creates the element list item. /// </summary> /// <param name="context">The context.</param> /// <param name="propertyType">Type of the property.</param> /// <param name="variableList">The variable list.</param> /// <param name="propertyExpressions">The property expressions.</param> /// <param name="attribute">The attribute.</param> /// <param name="customAttributes">The custom attributes.</param> /// <returns>The expression used to create the item.</returns> private NewExpression CreateElementListItem( PageBuilderContext context, Type propertyType, ICollection <ParameterExpression> variableList, List <Expression> propertyExpressions, ElementLocatorAttribute attribute, IEnumerable <object> customAttributes) { // Special case for lists Type concreteType; if (propertyType.IsClass && !propertyType.IsAbstract) { concreteType = propertyType; } else { var collectionType = this.GetElementCollectionType(); concreteType = collectionType.MakeGenericType(propertyType.GetGenericArguments()); } var constructor = concreteType.GetConstructors().First(); var parentListType = constructor.GetParameters().First().ParameterType; var parentListVariable = Expression.Variable(parentListType, "collectionParent"); variableList.Add(parentListVariable); // Save the current context item to create the parent, then set it back. var currentItem = context.CurrentElement; context.CurrentElement = new ExpressionData(parentListVariable, parentListType); propertyExpressions.AddRange(this.CreateHtmlObject(context, attribute, customAttributes)); context.CurrentElement = currentItem; return(Expression.New(constructor, parentListVariable, context.Browser.Expression)); }
/// <summary> /// Creates the HTML object for each property that is part of the parent. /// </summary> /// <param name="context">The context.</param> /// <param name="attribute">The attribute.</param> /// <param name="nativeAttributes">The native attributes.</param> /// <returns>The expressions needed to create the list</returns> private IEnumerable <Expression> CreateHtmlObject(PageBuilderContext context, ElementLocatorAttribute attribute, IEnumerable nativeAttributes) { var buildElement = context.CurrentElement ?? context.Document; var objectType = this.GetPropertyProxyType(buildElement.Type); var propConstructor = this.GetConstructor(objectType, context); if (propConstructor == null) { throw this.CreateConstructorException(buildElement.Name, objectType); } var itemVariable = buildElement.Expression; return(new[] { (Expression)Expression.Assign(itemVariable, Expression.New(propConstructor.Item1, propConstructor.Item2)), Expression.Call(Expression.Constant(this), this.assignMethodInfo, Expression.Convert(itemVariable, typeof(TElement)), Expression.Constant(attribute, typeof(ElementLocatorAttribute)), Expression.Constant(nativeAttributes, typeof(object[]))) }); }
/// <summary> /// Maps the object properties for the given object. /// </summary> /// <param name="expressions">The parent expression list.</param> /// <param name="objectType">Type of the object.</param> /// <param name="context">The page builder context.</param> private void MapObjectProperties(ICollection <Expression> expressions, Type objectType, PageBuilderContext context) { // ReSharper disable LoopCanBeConvertedToQuery foreach (var propertyInfo in objectType.GetProperties().Where(p => p.SetMethod != null && p.CanWrite && p.PropertyType.IsSupportedPropertyType(typeof(TElement)))) { var propertyType = propertyInfo.PropertyType; var attribute = propertyInfo.GetCustomAttributes(typeof(ElementLocatorAttribute), false).FirstOrDefault() as ElementLocatorAttribute; var customAttributes = this.GetCustomAttributes(propertyInfo); if (attribute == null && customAttributes.Length == 0) { continue; } // Final Properties var itemVariable = Expression.Variable(propertyType); context.CurrentElement = new ExpressionData(itemVariable, propertyType, propertyInfo.Name); var variableList = new List <ParameterExpression> { itemVariable }; var propertyExpressions = new List <Expression>(); // Special case for table driver wrapper if (propertyType.IsTableElementType()) { this.BindTableElementProperty(propertyInfo, context, variableList, propertyExpressions, attribute, customAttributes); } else if (propertyType.IsElementListType()) { var listItem = this.CreateElementListItem(context, propertyType, variableList, propertyExpressions, attribute, customAttributes); propertyExpressions.Add(Expression.Assign(itemVariable, listItem)); } else { //Normal path starts here //New up property and then check it for inner properties. var childContext = context.CreateChildContext(context.CurrentElement); propertyExpressions.AddRange(this.CreateHtmlObject(childContext, attribute, customAttributes)); this.MapObjectProperties(propertyExpressions, propertyType, childContext); } propertyExpressions.Add(Expression.Assign(Expression.Property(context.Document.Expression, propertyInfo), itemVariable)); expressions.Add(Expression.Block(variableList, propertyExpressions)); } }
/// <summary> /// Gets the constructor. /// </summary> /// <param name="itemType">Type of the item.</param> /// <param name="context">The page builder context.</param> /// <returns>The constructor information that matches.</returns> private Tuple <ConstructorInfo, IEnumerable <Expression> > GetConstructor(Type itemType, PageBuilderContext context) { ConstructorInfo emptyConstructor = null; foreach (var constructorInfo in itemType.GetConstructors(BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .OrderByDescending(c => c.GetParameters().Length)) { var parameters = constructorInfo.GetParameters(); if (parameters.Length == 0) { emptyConstructor = constructorInfo; continue; } var slots = new Expression[parameters.Length]; slots.Initialize(); for (var i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; var parameterType = parameter.ParameterType; if (context.Browser.Type.IsAssignableFrom(parameterType)) { slots[i] = Expression.Convert(context.Browser.Expression, context.Browser.Type); } else { slots[i] = this.FillConstructorParameter(parameterType, context.ParentElement, context.RootLocator); } } if (slots.All(s => s != null)) { return(new Tuple <ConstructorInfo, IEnumerable <Expression> >(constructorInfo, slots)); } } return(this.AllowEmptyConstructor && emptyConstructor != null ? new Tuple <ConstructorInfo, IEnumerable <Expression> >(emptyConstructor, new List <Expression>(0)) : null); }
public void TestCreateChildContextWhenMultipleContextsAreCreatedThenTheRootContextIsTheFirstParent() { var browser = new ExpressionData(null, typeof(object)); var document = new ExpressionData(null, typeof(object)); var parentElement = new ExpressionData(null, typeof(object)); var context = new PageBuilderContext(browser, parentElement, document); var child1 = new ExpressionData(null, typeof(object)); var child2 = new ExpressionData(null, typeof(object)); var childContext1 = context.CreateChildContext(child1); var childContext2 = childContext1.CreateChildContext(child2); Assert.AreSame(document, childContext1.ParentElement); Assert.AreSame(parentElement, childContext1.RootLocator); Assert.AreSame(child1, childContext2.ParentElement); Assert.AreSame(parentElement, childContext2.RootLocator); }