using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using Luticate2.Auth.Utils.Dbo; using Luticate2.Auth.Utils.Dbo.Result; using Luticate2.Auth.Utils.Exceptions; using Luticate2.Auth.Utils.Interfaces; namespace Luticate2.Auth.Utils.Business.ExpressionConverter { public interface ILuExpressionConverterVisitorOptions { IDictionary Parameters { get; } ILuConvertersTypeConverter TypeConverter { get; } ILuObjectConverterDescriptorOptions DescriptorOptions { get; } ILuExpressionParamReplaceVisitorOptions VisitorOptions { get; } } public class LuExpressionConverterVisitor : ExpressionVisitor { public ILuExpressionConverterVisitorOptions Options { get; } protected IServiceProvider ServiceProvider { get; } public LuExpressionConverterVisitor(ILuExpressionConverterVisitorOptions options, IServiceProvider serviceProvider) { Options = options; ServiceProvider = serviceProvider; } protected LuResult> StackSimpleExpression(Expression expression, Stack stack) { stack.Push(expression); if (expression is MemberExpression memberExpression) { if (memberExpression.Expression != null) { var result = StackSimpleExpression(memberExpression.Expression, stack); return result; } return LuResult>.Ok(stack); } else if (expression is MethodCallExpression methodCallExpression) { if (methodCallExpression.Object != null) { var result = StackSimpleExpression(methodCallExpression.Object, stack); return result; } return LuResult>.Ok(stack); } else { return LuResult>.Ok(stack); } } protected ILuObjectConverterDescriptor GetConverterDescriptor(Type typeFrom, Type typeTo) { var type = typeof(ILuObjectConverterDescriptor<,>); var gtype = type.MakeGenericType(typeFrom, typeTo); var descriptor = (ILuObjectConverterDescriptor) ServiceProvider.GetService(gtype); if (descriptor == null && typeFrom == typeTo) { descriptor = (ILuObjectConverterDescriptor) ServiceProvider.GetService(typeof(ILuObjectConverterDescriptorIdentity)); } return descriptor; } protected LuResult ConvertMemberExpression(MemberExpression memberExpression, Expression newExpression, Type typeTo) { var typeFrom = memberExpression.Expression == null ? memberExpression.Member.DeclaringType : memberExpression.Expression.Type; var descriptor = GetConverterDescriptor(typeFrom, typeTo); if (descriptor == null) { return LuResult.Error(LuStatus.InternalError.ToInt(), $"Could not find converter descriptor for {typeFrom} => {typeTo}"); } var valueExpression = descriptor.GetMemberValueExpression(memberExpression.Member, Options.DescriptorOptions); if (valueExpression == null) { return LuResult.Error(LuStatus.InternalError.ToInt(), $"Could not find converter descriptor lambda for {typeFrom} => {typeTo} => {memberExpression.Member}"); } if (valueExpression.Parameters[0].Type != typeTo) { return LuResult.Error(LuStatus.InternalError.ToInt(), $"Invalid conversion lambda for {typeFrom} => {typeTo}"); } if (newExpression != null) { Options.Parameters.Add(valueExpression.Parameters[0], newExpression); } var visitor = new LuExpressionParamReplaceVisitor(Options.VisitorOptions); newExpression = visitor.Visit(valueExpression.Body); if (newExpression != null) { Options.Parameters.Remove(valueExpression.Parameters[0]); } return LuResult.Ok(newExpression); } protected LuResult ConvertMethodCallExpression(MethodCallExpression methodCallExpression, Expression newExpression, Type typeTo) { var typeFrom = methodCallExpression.Object == null ? methodCallExpression.Method.DeclaringType : methodCallExpression.Object.Type; var descriptor = GetConverterDescriptor(typeFrom, typeTo); if (descriptor == null) { return LuResult.Error(LuStatus.InternalError.ToInt(), $"Could not find converter descriptor for {typeFrom} => {typeTo}"); } var valueExpression = descriptor.GetMethodValueExpression(methodCallExpression.Method, Options.DescriptorOptions); if (valueExpression == null) { return LuResult.Error(LuStatus.InternalError.ToInt(), $"Could not find converter descriptor lambda for {typeFrom} => {typeTo} => {methodCallExpression.Method}"); } if (valueExpression.Parameters.Count != methodCallExpression.Arguments.Count + 1) { return LuResult.Error(LuStatus.InternalError.ToInt(), $"Converter descriptor lambda has incorrect number of arguments for {typeFrom} => {typeTo} => {methodCallExpression.Method}"); } if (valueExpression.Parameters[0].Type != typeTo) { return LuResult.Error(LuStatus.InternalError.ToInt(), $"Invalid conversion lambda for {typeFrom} => {typeTo}"); } var visitorConverter = new LuExpressionConverterVisitor(Options, ServiceProvider); for (var i = 0; i < methodCallExpression.Arguments.Count; ++i) { var convertedArgument = visitorConverter.Visit(methodCallExpression.Arguments[i]); Options.Parameters.Add(valueExpression.Parameters[i + 1], convertedArgument); } Options.Parameters.Add(valueExpression.Parameters[0], newExpression); var visitor = new LuExpressionParamReplaceVisitor(Options.VisitorOptions); newExpression = visitor.Visit(valueExpression.Body); Options.Parameters.Remove(valueExpression.Parameters[0]); return LuResult.Ok(newExpression); } protected LuResult ReplaceExpression(Expression expression) { var stackResult = StackSimpleExpression(expression, new Stack()); if (!stackResult) { return stackResult.To(); } Expression newExpression; var currentExp = stackResult.Data.Pop(); if (currentExp is ParameterExpression parameterExpression) { if (!Options.Parameters.ContainsKey(parameterExpression)) { return LuResult.Error(LuStatus.InternalError.ToInt(), $"Could not find a conversion for parameter {parameterExpression}"); } newExpression = Options.Parameters[parameterExpression]; } else if (currentExp is MemberExpression memberExpression) { var typeFrom = memberExpression.Member.DeclaringType; var typeTo = Options.TypeConverter.ConvertType(typeFrom); var convertResult = ConvertMemberExpression(memberExpression, null, typeTo); if (!convertResult) { return convertResult; } newExpression = convertResult.Data; } else if (currentExp is MethodCallExpression methodCallExpression) { var typeFrom = methodCallExpression.Method.DeclaringType; var typeTo = Options.TypeConverter.ConvertType(typeFrom); var convertResult = ConvertMethodCallExpression(methodCallExpression, null, typeTo); if (!convertResult) { return convertResult; } newExpression = convertResult.Data; } else { var visitor = new LuExpressionConverterVisitor(Options, ServiceProvider); newExpression = visitor.Visit(currentExp); } if (newExpression == null) { return LuResult.Error(LuStatus.InternalError.ToInt(), $"Invalid simple expression first member: {currentExp}", ""); } while (stackResult.Data.Any()) { currentExp = stackResult.Data.Pop(); if (currentExp is MemberExpression memberExpression) { var typeTo = newExpression.Type; var convertResult = ConvertMemberExpression(memberExpression, newExpression, typeTo); if (!convertResult) { return convertResult; } newExpression = convertResult.Data; } else if (currentExp is MethodCallExpression methodCallExpression) { var typeTo = newExpression.Type; var convertResult = ConvertMethodCallExpression(methodCallExpression, newExpression, typeTo); if (!convertResult) { return convertResult; } newExpression = convertResult.Data; } else { return LuResult.Error(LuStatus.InternalError.ToInt(), $"Unknown expression type (should not happen) {currentExp}"); } } return LuResult.Ok(newExpression); } protected override Expression VisitMember(MemberExpression node) { return ReplaceExpression(node).ThrowIfNotSuccess().Data; } protected override Expression VisitMethodCall(MethodCallExpression node) { return ReplaceExpression(node).ThrowIfNotSuccess().Data; } protected override Expression VisitParameter(ParameterExpression node) { if (!Options.Parameters.ContainsKey(node)) { LuResult.Error(LuStatus.InternalError.ToInt(), $"Could not find a conversion for parameter {node}").Throw(); } return Options.Parameters[node]; } protected override Expression VisitLambda(Expression node) { var convertedParams = new List(); foreach (var parameter in node.Parameters) { var convertedParam = Expression.Parameter(Options.TypeConverter.ConvertType(parameter.Type)); Options.Parameters.Add(parameter, convertedParam); convertedParams.Add(convertedParam); } var convertedBody = Visit(node.Body); foreach (var parameter in node.Parameters) { Options.Parameters.Remove(parameter); } var convertedLambda = Expression.Lambda(convertedBody, convertedParams); return convertedLambda; } } }