AyCode.Blazor/AyCode.Blazor.Components/Services/ExpressionHelpers/AcExpressionSerializerVisit...

430 lines
12 KiB
C#

using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace AyCode.Blazor.Components.Services.ExpressionHelpers;
/// <summary>
/// Expression visitor that serializes an Expression tree to AcExpressionNode DTO.
/// Handles all common expression types recursively.
/// </summary>
public class AcExpressionSerializerVisitor : ExpressionVisitor
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new JsonStringEnumConverter() }
};
private readonly Dictionary<ParameterExpression, int> _parameterIndexes = new();
private int _nextParameterIndex;
// Stack to collect converted nodes
private readonly Stack<AcExpressionNode> _nodeStack = new();
/// <summary>
/// Converts an Expression to an AcExpressionNode DTO.
/// </summary>
public AcExpressionNode Convert(Expression expression)
{
_nodeStack.Clear();
_parameterIndexes.Clear();
_nextParameterIndex = 0;
VisitAndConvert(expression);
return _nodeStack.Count != 1 ? throw new InvalidOperationException($"Expected 1 node on stack, found {_nodeStack.Count}") : _nodeStack.Pop();
}
/// <summary>
/// Serializes an Expression to JSON string.
/// </summary>
public string ToJson(Expression expression)
{
var node = Convert(expression);
return JsonSerializer.Serialize(node, JsonOptions);
}
private void VisitAndConvert(Expression expression)
{
Visit(expression);
}
protected override Expression VisitBinary(BinaryExpression node)
{
VisitAndConvert(node.Left);
var left = _nodeStack.Pop();
VisitAndConvert(node.Right);
var right = _nodeStack.Pop();
_nodeStack.Push(new AcExpressionNode
{
NodeType = node.NodeType,
TypeName = node.Type.FullName,
Left = left,
Right = right
});
return node;
}
protected override Expression VisitUnary(UnaryExpression node)
{
VisitAndConvert(node.Operand);
var operand = _nodeStack.Pop();
_nodeStack.Push(new AcExpressionNode
{
NodeType = node.NodeType,
TypeName = node.Type.FullName,
Operand = operand
});
return node;
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
// Register parameters with indexes
var parameters = new List<ParameterNode>();
foreach (var param in node.Parameters)
{
var index = _nextParameterIndex++;
_parameterIndexes[param] = index;
parameters.Add(new ParameterNode
{
Name = param.Name ?? $"p{index}",
TypeName = param.Type.FullName ?? param.Type.Name,
Index = index
});
}
VisitAndConvert(node.Body);
var body = _nodeStack.Pop();
_nodeStack.Push(new AcExpressionNode
{
NodeType = ExpressionType.Lambda,
TypeName = node.Type.FullName,
Body = body,
Parameters = parameters
});
return node;
}
protected override Expression VisitParameter(ParameterExpression node)
{
var index = _parameterIndexes.GetValueOrDefault(node, -1);
_nodeStack.Push(new AcExpressionNode
{
NodeType = ExpressionType.Parameter,
TypeName = node.Type.FullName,
ParameterName = node.Name,
ParameterIndex = index
});
return node;
}
protected override Expression VisitMember(MemberExpression node)
{
// Check if this is a closure variable access (captured variable)
if (IsClosureAccess(node))
{
var value = EvaluateClosureValue(node);
_nodeStack.Push(new AcExpressionNode
{
NodeType = ExpressionType.Constant,
TypeName = node.Type.FullName,
Value = SerializeValue(value)
});
return node;
}
AcExpressionNode? objectNode = null;
if (node.Expression != null)
{
VisitAndConvert(node.Expression);
objectNode = _nodeStack.Pop();
}
_nodeStack.Push(new AcExpressionNode
{
NodeType = ExpressionType.MemberAccess,
TypeName = node.Type.FullName,
MemberName = node.Member.Name,
Object = objectNode,
DeclaringType = node.Member.DeclaringType?.FullName
});
return node;
}
protected override Expression VisitConstant(ConstantExpression node)
{
_nodeStack.Push(new AcExpressionNode
{
NodeType = ExpressionType.Constant,
TypeName = node.Type.FullName,
Value = SerializeValue(node.Value)
});
return node;
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
AcExpressionNode? objectNode = null;
if (node.Object != null)
{
VisitAndConvert(node.Object);
objectNode = _nodeStack.Pop();
}
var arguments = new List<AcExpressionNode>();
foreach (var arg in node.Arguments)
{
VisitAndConvert(arg);
arguments.Add(_nodeStack.Pop());
}
var genericArgs = node.Method.IsGenericMethod
? node.Method.GetGenericArguments().Select(t => t.FullName ?? t.Name).ToList()
: null;
_nodeStack.Push(new AcExpressionNode
{
NodeType = ExpressionType.Call,
TypeName = node.Type.FullName,
MethodName = node.Method.Name,
Object = objectNode,
Arguments = arguments,
DeclaringType = node.Method.DeclaringType?.FullName,
GenericArguments = genericArgs
});
return node;
}
protected override Expression VisitConditional(ConditionalExpression node)
{
VisitAndConvert(node.Test);
var test = _nodeStack.Pop();
VisitAndConvert(node.IfTrue);
var ifTrue = _nodeStack.Pop();
VisitAndConvert(node.IfFalse);
var ifFalse = _nodeStack.Pop();
_nodeStack.Push(new AcExpressionNode
{
NodeType = ExpressionType.Conditional,
TypeName = node.Type.FullName,
Test = test,
IfTrue = ifTrue,
IfFalse = ifFalse
});
return node;
}
protected override Expression VisitNew(NewExpression node)
{
var arguments = new List<AcExpressionNode>();
foreach (var arg in node.Arguments)
{
VisitAndConvert(arg);
arguments.Add(_nodeStack.Pop());
}
_nodeStack.Push(new AcExpressionNode
{
NodeType = ExpressionType.New,
TypeName = node.Type.FullName,
ConstructorArguments = arguments
});
return node;
}
protected override Expression VisitMemberInit(MemberInitExpression node)
{
var arguments = new List<AcExpressionNode>();
foreach (var arg in node.NewExpression.Arguments)
{
VisitAndConvert(arg);
arguments.Add(_nodeStack.Pop());
}
_nodeStack.Push(new AcExpressionNode
{
NodeType = ExpressionType.MemberInit,
TypeName = node.Type.FullName,
ConstructorArguments = arguments,
MemberBindings = node.Bindings.Select(ConvertMemberBinding).ToList()
});
return node;
}
protected override Expression VisitNewArray(NewArrayExpression node)
{
var elements = new List<AcExpressionNode>();
foreach (var expr in node.Expressions)
{
VisitAndConvert(expr);
elements.Add(_nodeStack.Pop());
}
_nodeStack.Push(new AcExpressionNode
{
NodeType = node.NodeType,
TypeName = node.Type.FullName,
Elements = elements
});
return node;
}
protected override Expression VisitTypeBinary(TypeBinaryExpression node)
{
VisitAndConvert(node.Expression);
var operand = _nodeStack.Pop();
_nodeStack.Push(new AcExpressionNode
{
NodeType = node.NodeType,
TypeName = node.TypeOperand.FullName,
Operand = operand
});
return node;
}
protected override Expression VisitInvocation(InvocationExpression node)
{
VisitAndConvert(node.Expression);
var objectNode = _nodeStack.Pop();
var arguments = new List<AcExpressionNode>();
foreach (var arg in node.Arguments)
{
VisitAndConvert(arg);
arguments.Add(_nodeStack.Pop());
}
_nodeStack.Push(new AcExpressionNode
{
NodeType = ExpressionType.Invoke,
TypeName = node.Type.FullName,
Object = objectNode,
Arguments = arguments
});
return node;
}
#region Helper Methods
private MemberBindingNode ConvertMemberBinding(MemberBinding binding)
{
return binding switch
{
MemberAssignment assignment => ConvertMemberAssignment(assignment),
MemberMemberBinding memberBinding => new MemberBindingNode
{
MemberName = memberBinding.Member.Name,
BindingType = MemberBindingType.MemberBinding,
Bindings = memberBinding.Bindings.Select(ConvertMemberBinding).ToList()
},
MemberListBinding listBinding => new MemberBindingNode
{
MemberName = listBinding.Member.Name,
BindingType = MemberBindingType.ListBinding,
Initializers = listBinding.Initializers
.Select(i => i.Arguments.Select(ConvertArgument).ToList())
.ToList()
},
_ => throw new NotSupportedException($"Member binding type '{binding.BindingType}' is not supported.")
};
}
private MemberBindingNode ConvertMemberAssignment(MemberAssignment assignment)
{
VisitAndConvert(assignment.Expression);
var expr = _nodeStack.Pop();
return new MemberBindingNode
{
MemberName = assignment.Member.Name,
BindingType = MemberBindingType.Assignment,
Expression = expr
};
}
private AcExpressionNode ConvertArgument(Expression expression)
{
VisitAndConvert(expression);
return _nodeStack.Pop();
}
private static bool IsClosureAccess(MemberExpression node)
{
return node.Expression switch
{
ConstantExpression => true,
MemberExpression nested => IsClosureAccess(nested),
_ => false
};
}
private static object? EvaluateClosureValue(MemberExpression node)
{
var objectStack = new Stack<MemberExpression>();
Expression? current = node;
while (current is MemberExpression me)
{
objectStack.Push(me);
current = me.Expression;
}
if (current is not ConstantExpression constant)
throw new InvalidOperationException("Expected constant at root of closure access.");
object? value = constant.Value;
while (objectStack.Count > 0)
{
var me = objectStack.Pop();
value = me.Member switch
{
FieldInfo fi => fi.GetValue(value),
PropertyInfo pi => pi.GetValue(value),
_ => throw new InvalidOperationException($"Unsupported member type: {me.Member.GetType()}")
};
}
return value;
}
private static string? SerializeValue(object? value)
{
if (value == null) return null;
// Handle IQueryable source - serialize as placeholder
if (value is IQueryable)
return null;
return JsonSerializer.Serialize(value, JsonOptions);
}
#endregion
}