/Atlassian.Jira/Linq/ExpressionEvaluator.cs
C# | 132 lines | 95 code | 12 blank | 25 comment | 10 complexity | 96e77b632f884522d48537fd844a6dcd MD5 | raw file
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Linq.Expressions;
-
- namespace Atlassian.Jira.Linq
- {
- /// <summary>
- /// Evaluates subtrees that contain local variables.
- /// </summary>
- /// <remarks>
- /// Thanks to http://blogs.msdn.com/b/mattwar/archive/2007/08/01/linq-building-an-iqueryable-provider-part-iii.aspx
- /// for providing the source for this class
- /// </remarks>
- internal static class ExpressionEvaluator
- {
- /// <summary>
- /// Performs evaluation & replacement of independent sub-trees
- /// </summary>
- /// <param name="expression">The root of the expression tree.</param>
- /// <returns>A new tree with sub-trees evaluated and replaced.</returns>
- public static Expression PartialEval(Expression expression)
- {
- return PartialEval(expression, ExpressionEvaluator.CanBeEvaluatedLocally);
- }
-
- /// <summary>
- /// Performs evaluation & replacement of independent sub-trees
- /// </summary>
- /// <param name="expression">The root of the expression tree.</param>
- /// <param name="fnCanBeEvaluated">A function that decides whether a given expression node can be part of the local function.</param>
- /// <returns>A new tree with sub-trees evaluated and replaced.</returns>
- public static Expression PartialEval(Expression expression, Func<Expression, bool> fnCanBeEvaluated)
- {
- return new SubtreeEvaluator(new Nominator(fnCanBeEvaluated).Nominate(expression)).Eval(expression);
- }
-
- private static bool CanBeEvaluatedLocally(Expression expression)
- {
- return expression.NodeType != ExpressionType.Parameter;
- }
-
- /// <summary>
- /// Evaluates & replaces sub-trees when first candidate is reached (top-down)
- /// </summary>
- class SubtreeEvaluator : ExpressionVisitor
- {
- HashSet<Expression> candidates;
-
- internal SubtreeEvaluator(HashSet<Expression> candidates)
- {
- this.candidates = candidates;
- }
-
- internal Expression Eval(Expression exp)
- {
- return this.Visit(exp);
- }
-
- public override Expression Visit(Expression exp)
- {
- if (exp == null)
- {
- return null;
- }
- if (this.candidates.Contains(exp))
- {
- return this.Evaluate(exp);
- }
- return base.Visit(exp);
- }
-
- private Expression Evaluate(Expression e)
- {
- if (e.NodeType == ExpressionType.Constant)
- {
- return e;
- }
- LambdaExpression lambda = Expression.Lambda(e);
- Delegate fn = lambda.Compile();
- return Expression.Constant(fn.DynamicInvoke(null), e.Type);
- }
- }
-
- /// <summary>
- /// Performs bottom-up analysis to determine which nodes can possibly
- /// be part of an evaluated sub-tree.
- /// </summary>
- class Nominator : ExpressionVisitor
- {
- Func<Expression, bool> fnCanBeEvaluated;
- HashSet<Expression> candidates;
- bool cannotBeEvaluated;
-
- internal Nominator(Func<Expression, bool> fnCanBeEvaluated)
- {
- this.fnCanBeEvaluated = fnCanBeEvaluated;
- }
-
- internal HashSet<Expression> Nominate(Expression expression)
- {
- this.candidates = new HashSet<Expression>();
- this.Visit(expression);
- return this.candidates;
- }
-
- public override Expression Visit(Expression expression)
- {
- if (expression != null)
- {
- bool saveCannotBeEvaluated = this.cannotBeEvaluated;
- this.cannotBeEvaluated = false;
- base.Visit(expression);
- if (!this.cannotBeEvaluated)
- {
- if (this.fnCanBeEvaluated(expression))
- {
- this.candidates.Add(expression);
- }
- else
- {
- this.cannotBeEvaluated = true;
- }
- }
- this.cannotBeEvaluated |= saveCannotBeEvaluated;
- }
- return expression;
- }
- }
- }
- }