/* 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);
}
}
}