/* Copyright 2010-present MongoDB Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Collections.Generic; namespace MongoDB.Bson.Serialization { /// /// A helper class used to create and compile delegates for creator maps. /// public class CreatorMapDelegateCompiler : ExpressionVisitor { // private fields private ParameterExpression _prototypeParameter; private Dictionary _parameters; // public methods /// /// Creates and compiles a delegate that calls a constructor. /// /// The constructor. /// A delegate that calls the constructor. public Delegate CompileConstructorDelegate(ConstructorInfo constructorInfo) { // build and compile the following delegate: // (p1, p2, ...) => new TClass(p1, p2, ...) var parameters = constructorInfo.GetParameters().Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray(); var body = Expression.New(constructorInfo, parameters); var lambda = Expression.Lambda(body, parameters); return lambda.Compile(); } /// /// Creates and compiles a delegate from a lambda expression. /// /// The type of the class. /// The lambda expression. /// The arguments for the delegate's parameters. /// A delegate. public Delegate CompileCreatorDelegate(Expression> creatorLambda, out IEnumerable arguments) { // transform c => expression (where c is the prototype parameter) // to (p1, p2, ...) => expression' where expression' is expression with every c.X replaced by p# _prototypeParameter = creatorLambda.Parameters[0]; _parameters = new Dictionary(); var body = Visit(creatorLambda.Body); var lambda = Expression.Lambda(body, _parameters.Values.ToArray()); var @delegate = lambda.Compile(); arguments = _parameters.Keys.ToArray(); return @delegate; } /// /// Creates and compiles a delegate that calls a factory method. /// /// the method. /// A delegate that calls the factory method. public Delegate CompileFactoryMethodDelegate(MethodInfo methodInfo) { // build and compile the following delegate: // (p1, p2, ...) => factoryMethod(p1, p2, ...) var parameters = methodInfo.GetParameters().Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray(); var body = Expression.Call(methodInfo, parameters); var lambda = Expression.Lambda(body, parameters); return lambda.Compile(); } // protected methods /// /// Visits a MemberExpression. /// /// The MemberExpression. /// The MemberExpression (possibly modified). protected override Expression VisitMember(MemberExpression node) { if (node.Expression == _prototypeParameter) { var memberInfo = node.Member; ParameterExpression parameter; if (!_parameters.TryGetValue(memberInfo, out parameter)) { var parameterName = string.Format("_p{0}_", _parameters.Count + 1); // avoid naming conflicts with body parameter = Expression.Parameter(node.Type, parameterName); _parameters.Add(memberInfo, parameter); } return parameter; } return base.VisitMember(node); } /// /// Visits a ParameterExpression. /// /// The ParameterExpression. /// The ParameterExpression (possibly modified). protected override Expression VisitParameter(ParameterExpression node) { if (node == _prototypeParameter) { throw new BsonSerializationException("The only operations allowed on the prototype parameter are accessing a field or property."); } return base.VisitParameter(node); } } }