/* 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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq.Expressions;
namespace MongoDB.Bson.Serialization
{
// TODO: this class duplicates a similar class in MongoDB.Driver.dll
// when we move to .NET Framework 4 we can use .NET's version of ExpressionVisitor and eliminate the duplication
///
/// An abstract base class for an Expression visitor.
///
public abstract class ExpressionVisitor
{
// constructors
///
/// Initializes a new instance of the ExpressionVisitor class.
///
protected ExpressionVisitor()
{
}
// protected methods
///
/// Visits an Expression.
///
/// The Expression.
/// The Expression (posibly modified).
protected virtual Expression Visit(Expression node)
{
if (node == null)
{
return node;
}
switch (node.NodeType)
{
case ExpressionType.Negate:
case ExpressionType.NegateChecked:
case ExpressionType.Not:
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
case ExpressionType.ArrayLength:
case ExpressionType.Quote:
case ExpressionType.TypeAs:
return this.VisitUnary((UnaryExpression)node);
case ExpressionType.Add:
case ExpressionType.AddChecked:
case ExpressionType.Subtract:
case ExpressionType.SubtractChecked:
case ExpressionType.Multiply:
case ExpressionType.MultiplyChecked:
case ExpressionType.Divide:
case ExpressionType.Modulo:
case ExpressionType.And:
case ExpressionType.AndAlso:
case ExpressionType.Or:
case ExpressionType.OrElse:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.Coalesce:
case ExpressionType.ArrayIndex:
case ExpressionType.RightShift:
case ExpressionType.LeftShift:
case ExpressionType.ExclusiveOr:
return this.VisitBinary((BinaryExpression)node);
case ExpressionType.TypeIs:
return this.VisitTypeBinary((TypeBinaryExpression)node);
case ExpressionType.Conditional:
return this.VisitConditional((ConditionalExpression)node);
case ExpressionType.Constant:
return this.VisitConstant((ConstantExpression)node);
case ExpressionType.Parameter:
return this.VisitParameter((ParameterExpression)node);
case ExpressionType.MemberAccess:
return this.VisitMember((MemberExpression)node);
case ExpressionType.Call:
return this.VisitMethodCall((MethodCallExpression)node);
case ExpressionType.Lambda:
return this.VisitLambda((LambdaExpression)node);
case ExpressionType.New:
return this.VisitNew((NewExpression)node);
case ExpressionType.NewArrayInit:
case ExpressionType.NewArrayBounds:
return this.VisitNewArray((NewArrayExpression)node);
case ExpressionType.Invoke:
return this.VisitInvocation((InvocationExpression)node);
case ExpressionType.MemberInit:
return this.VisitMemberInit((MemberInitExpression)node);
case ExpressionType.ListInit:
return this.VisitListInit((ListInitExpression)node);
default:
throw new Exception(string.Format("Unhandled expression type: '{0}'", node.NodeType));
}
}
///
/// Visits an Expression list.
///
/// The Expression list.
/// The Expression list (possibly modified).
protected ReadOnlyCollection Visit(ReadOnlyCollection nodes)
{
List list = null;
for (int i = 0, n = nodes.Count; i < n; i++)
{
Expression node = this.Visit(nodes[i]);
if (list != null)
{
list.Add(node);
}
else if (node != nodes[i])
{
list = new List(n);
for (int j = 0; j < i; j++)
{
list.Add(nodes[j]);
}
list.Add(node);
}
}
if (list != null)
{
return list.AsReadOnly();
}
return nodes;
}
///
/// Visits a BinaryExpression.
///
/// The BinaryExpression.
/// The BinaryExpression (possibly modified).
protected virtual Expression VisitBinary(BinaryExpression node)
{
Expression left = this.Visit(node.Left);
Expression right = this.Visit(node.Right);
Expression conversion = this.Visit(node.Conversion);
if (left != node.Left || right != node.Right || conversion != node.Conversion)
{
if (node.NodeType == ExpressionType.Coalesce && node.Conversion != null)
{
return Expression.Coalesce(left, right, conversion as LambdaExpression);
}
else
{
return Expression.MakeBinary(node.NodeType, left, right, node.IsLiftedToNull, node.Method);
}
}
return node;
}
///
/// Visits a ConditionalExpression.
///
/// The ConditionalExpression.
/// The ConditionalExpression (possibly modified).
protected virtual Expression VisitConditional(ConditionalExpression node)
{
Expression test = this.Visit(node.Test);
Expression ifTrue = this.Visit(node.IfTrue);
Expression ifFalse = this.Visit(node.IfFalse);
if (test != node.Test || ifTrue != node.IfTrue || ifFalse != node.IfFalse)
{
return Expression.Condition(test, ifTrue, ifFalse);
}
return node;
}
///
/// Visits a ConstantExpression.
///
/// The ConstantExpression.
/// The ConstantExpression (possibly modified).
protected virtual Expression VisitConstant(ConstantExpression node)
{
return node;
}
///
/// Visits an ElementInit.
///
/// The ElementInit.
/// The ElementInit (possibly modified).
protected virtual ElementInit VisitElementInit(ElementInit node)
{
ReadOnlyCollection arguments = this.Visit(node.Arguments);
if (arguments != node.Arguments)
{
return Expression.ElementInit(node.AddMethod, arguments);
}
return node;
}
// TODO: the .NET Framework 4 version of ExpressionVisitor does not have a method called VisitElementInitializerList
// leaving this method for now, though perhaps it could be replaced with Visit(ReadOnlyCollection)?
///
/// Visits an ElementInit list.
///
/// The ElementInit list.
/// The ElementInit list (possibly modified).
protected virtual IEnumerable VisitElementInitList(
ReadOnlyCollection nodes)
{
List list = null;
for (int i = 0, n = nodes.Count; i < n; i++)
{
ElementInit node = this.VisitElementInit(nodes[i]);
if (list != null)
{
list.Add(node);
}
else if (node != nodes[i])
{
list = new List(n);
for (int j = 0; j < i; j++)
{
list.Add(nodes[j]);
}
list.Add(node);
}
}
if (list != null)
{
return list;
}
return nodes;
}
///
/// Visits an InvocationExpression.
///
/// The InvocationExpression.
/// The InvocationExpression (possibly modified).
protected virtual Expression VisitInvocation(InvocationExpression node)
{
IEnumerable args = this.Visit(node.Arguments);
Expression expr = this.Visit(node.Expression);
if (args != node.Arguments || expr != node.Expression)
{
return Expression.Invoke(expr, args);
}
return node;
}
// TODO: in .NET Framework 4 VisitLambda takes an Expression instead of Lambda
// probably not worthing changing in our version of ExpressionVisitor
///
/// Visits a LambdaExpression.
///
/// The LambdaExpression.
/// The LambdaExpression (possibly modified).
protected virtual Expression VisitLambda(LambdaExpression node)
{
Expression body = this.Visit(node.Body);
if (body != node.Body)
{
return Expression.Lambda(node.Type, body, node.Parameters);
}
return node;
}
///
/// Visits a ListInitExpression.
///
/// The ListInitExpression.
/// The ListInitExpression (possibly modified).
protected virtual Expression VisitListInit(ListInitExpression node)
{
NewExpression n = this.VisitNew(node.NewExpression);
IEnumerable initializers = this.VisitElementInitList(node.Initializers);
if (n != node.NewExpression || initializers != node.Initializers)
{
return Expression.ListInit(n, initializers);
}
return node;
}
///
/// Visits a MemberExpression.
///
/// The MemberExpression.
/// The MemberExpression (possibly modified).
protected virtual Expression VisitMember(MemberExpression node)
{
Expression exp = this.Visit(node.Expression);
if (exp != node.Expression)
{
return Expression.MakeMemberAccess(exp, node.Member);
}
return node;
}
///
/// Visits a MemberAssignment.
///
/// The MemberAssignment.
/// The MemberAssignment (possibly modified).
protected virtual MemberAssignment VisitMemberAssignment(MemberAssignment node)
{
Expression e = this.Visit(node.Expression);
if (e != node.Expression)
{
return Expression.Bind(node.Member, e);
}
return node;
}
///
/// Visits a MemberBinding.
///
/// The MemberBinding.
/// The MemberBinding (possibly modified).
protected virtual MemberBinding VisitMemberBinding(MemberBinding node)
{
switch (node.BindingType)
{
case MemberBindingType.Assignment:
return this.VisitMemberAssignment((MemberAssignment)node);
case MemberBindingType.MemberBinding:
return this.VisitMemberMemberBinding((MemberMemberBinding)node);
case MemberBindingType.ListBinding:
return this.VisitMemberListBinding((MemberListBinding)node);
default:
throw new Exception(string.Format("Unhandled binding type '{0}'", node.BindingType));
}
}
// TODO: the .NET Framework 4 version of ExpressionVisitor does not have a method called VisitMemberBindingList
// leaving this method for now, though perhaps it could be replaced with Visit(ReadOnlyCollection)?
///
/// Visits a MemberBinding list.
///
/// The MemberBinding list.
/// The MemberBinding list (possibly modified).
protected virtual IEnumerable VisitMemberBindingList(ReadOnlyCollection nodes)
{
List list = null;
for (int i = 0, n = nodes.Count; i < n; i++)
{
MemberBinding node = this.VisitMemberBinding(nodes[i]);
if (list != null)
{
list.Add(node);
}
else if (node != nodes[i])
{
list = new List(n);
for (int j = 0; j < i; j++)
{
list.Add(nodes[j]);
}
list.Add(node);
}
}
if (list != null)
{
return list;
}
return nodes;
}
///
/// Visits a MemberInitExpression.
///
/// The MemberInitExpression.
/// The MemberInitExpression (possibly modified).
protected virtual Expression VisitMemberInit(MemberInitExpression node)
{
NewExpression n = this.VisitNew(node.NewExpression);
IEnumerable bindings = this.VisitMemberBindingList(node.Bindings);
if (n != node.NewExpression || bindings != node.Bindings)
{
return Expression.MemberInit(n, bindings);
}
return node;
}
///
/// Visits a MemberListBinding.
///
/// The MemberListBinding.
/// The MemberListBinding (possibly modified).
protected virtual MemberListBinding VisitMemberListBinding(MemberListBinding node)
{
IEnumerable initializers = this.VisitElementInitList(node.Initializers);
if (initializers != node.Initializers)
{
return Expression.ListBind(node.Member, initializers);
}
return node;
}
///
/// Visits a MemberMemberBinding.
///
/// The MemberMemberBinding.
/// The MemberMemberBinding (possibly modified).
protected virtual MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node)
{
IEnumerable bindings = this.VisitMemberBindingList(node.Bindings);
if (bindings != node.Bindings)
{
return Expression.MemberBind(node.Member, bindings);
}
return node;
}
///
/// Visits a MethodCallExpression.
///
/// The MethodCallExpression.
/// The MethodCallExpression (possibly modified).
protected virtual Expression VisitMethodCall(MethodCallExpression node)
{
Expression obj = this.Visit(node.Object);
IEnumerable args = this.Visit(node.Arguments);
if (obj != node.Object || args != node.Arguments)
{
return Expression.Call(obj, node.Method, args);
}
return node;
}
///
/// Visits a NewExpression.
///
/// The NewExpression.
/// The NewExpression (possibly modified).
protected virtual NewExpression VisitNew(NewExpression node)
{
IEnumerable args = this.Visit(node.Arguments);
if (args != node.Arguments)
{
if (node.Members != null)
{
return Expression.New(node.Constructor, args, node.Members);
}
else
{
return Expression.New(node.Constructor, args);
}
}
return node;
}
///
/// Visits a NewArrayExpression.
///
/// The NewArrayExpression.
/// The NewArrayExpression (possibly modified).
protected virtual Expression VisitNewArray(NewArrayExpression node)
{
IEnumerable exprs = this.Visit(node.Expressions);
if (exprs != node.Expressions)
{
if (node.NodeType == ExpressionType.NewArrayInit)
{
return Expression.NewArrayInit(node.Type.GetElementType(), exprs);
}
else
{
return Expression.NewArrayBounds(node.Type.GetElementType(), exprs);
}
}
return node;
}
///
/// Visits a ParameterExpression.
///
/// The ParameterExpression.
/// The ParameterExpression (possibly modified).
protected virtual Expression VisitParameter(ParameterExpression node)
{
return node;
}
///
/// Visits a TypeBinaryExpression.
///
/// The TypeBinaryExpression.
/// The TypeBinaryExpression (possibly modified).
protected virtual Expression VisitTypeBinary(TypeBinaryExpression node)
{
Expression expr = this.Visit(node.Expression);
if (expr != node.Expression)
{
return Expression.TypeIs(expr, node.TypeOperand);
}
return node;
}
///
/// Visits a UnaryExpression.
///
/// The UnaryExpression.
/// The UnaryExpression (possibly modified).
protected virtual Expression VisitUnary(UnaryExpression node)
{
Expression operand = this.Visit(node.Operand);
if (operand != node.Operand)
{
return Expression.MakeUnary(node.NodeType, operand, node.Type, node.Method);
}
return node;
}
}
}