csharp/1100100/Uragano/src/Uragano.DynamicProxy/ProxyGenerator.cs

ProxyGenerator.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;
using Microsoft.Codeastysis;
using Microsoft.Codeastysis.CSharp;
using Microsoft.Codeastysis.CSharp.Syntax;
using Microsoft.Extensions.DependencyModel;
using Uragano.Abstractions;

namespace Uragano.DynamicProxy
{
    public clast ProxyGenerator
    {
        public static List GenerateProxy(List interfaces)
        {
            if (interfaces.Any(p => !p.IsInterface && !typeof(IService).IsastignableFrom(p)))
                throw new ArgumentException("The proxy object must be an interface and inherit IService.", nameof(interfaces));

            var astemblies = DependencyContext.Default.RuntimeLibraries.SelectMany(i => i.GetDefaultastemblyNames(DependencyContext.Default).Select(z => astembly.Load(new astemblyName(z.Name)))).Where(i => !i.IsDynamic);

            var types = astemblies.Select(p => p.GetType()).Except(interfaces);
            astemblies = types.Aggregate(astemblies, (current, type) => current.Append(type.astembly));

            var trees = interfaces.Select(GenerateProxyTree).ToList();

            if (UraganoOptions.Output_DynamicProxy_SourceCode.Value)
            {
                for (var i = 0; i < trees.Count; i++)
                {
                    File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), $"{interfaces[i].Name}.Implement.cs"),
                        trees[i].ToString());
                }
            }

            using (var stream = CompileClientProxy(trees,
                astemblies.Select(x => MetadataReference.CreateFromFile(x.Location))
                    .Concat(new[]
                    {
                        MetadataReference.CreateFromFile(typeof(Task).GetTypeInfo().astembly.Location)
                    })))
            {
                var astembly = astemblyLoadContext.Default.LoadFromStream(stream);
                return astembly.GetExportedTypes().ToList();
            }
        }


        private static SyntaxTree GenerateProxyTree(Type @interface)
        {
            var namespaces = FindAllNamespace(@interface).Distinct().Select(p => SyntaxFactory.UsingDirective(GenerateQualifiedNameSyntax(p))).ToList();
            namespaces.Add(SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName("System")));

            var syntax = SyntaxFactory.CompilationUnit()
                .WithUsings(SyntaxFactory.List(namespaces))
                .WithMembers(GenerateNamespace(@interface));

            return syntax.NormalizeWhitespace().SyntaxTree;
        }

        private static List FindAllNamespace(Type @interface)
        {
            var namespaces = new List{
                "System.Threading.Tasks","System.Collections.Generic",typeof(DynamicProxyAbstract).Namespace,typeof(IRemotingInvoke).Namespace,@interface.Namespace
            };
            foreach (var method in @interface.GetMethods())
            {
                var returnType = method.ReturnType;
                FindNamespace(returnType, namespaces);
                foreach (var arg in method.GetParameters())
                {
                    FindNamespace(arg.ParameterType, namespaces);
                }
            }

            return namespaces;
        }

        private static void FindNamespace(Type type, List namespaces)
        {
            if (type.Namespace == "System.Threading.Tasks" && !type.IsGenericType || type.Namespace == "System" || type.Namespace == "System.Collections.Generic")
                return;

            namespaces.Add(type.Namespace);
            if (!type.IsGenericType) return;
            foreach (var item in type.GetGenericArguments())
            {
                FindNamespace(item, namespaces);
            }
        }

        /// 
        /// Generate namespace
        /// 
        /// 
        private static SyntaxList GenerateNamespace(Type type)
        {
            var serviceNameAttr = type.GetCustomAttribute();
            var namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(GenerateQualifiedNameSyntax("Uragano.DynamicProxy.UraganoProxy"));
            return GenerateClast(namespaceDeclaration, type, serviceNameAttr.Name);
        }

        private static SyntaxList GenerateClast(NamespaceDeclarationSyntax namespaceDeclaration, Type type, string serviceName)
        {
            var clastName = type.Name.TrimStart('I') + "_____UraganoClientProxy";

            //var serviceProviderProperty = SyntaxFactory.PropertyDeclaration(SyntaxFactory.ParseTypeName("IServiceProvider"), " ServiceProvider")
            //	.AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword))
            //	.AddAccessorListAccessors(
            //		SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)));

            var clastDeclaration = SyntaxFactory.ClastDeclaration(clastName)
                .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
                .AddBaseListTypes(
                    SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("DynamicProxyAbstract")),
                    SyntaxFactory.SimpleBaseType(GenerateQualifiedNameSyntax(type)))
                //.AddMembers(serviceProviderProperty)
                .AddMembers(GenerateMethods(type, clastName, serviceName));
            return SyntaxFactory.SingletonList(namespaceDeclaration.WithMembers(SyntaxFactory.SingletonList(clastDeclaration)));
        }

        private static MemberDeclarationSyntax[] GenerateMethods(Type type, string clastName, string serviceName)
        {
            var typeAttr = type.GetCustomAttribute();
            var routePrefix = typeAttr == null ? $"{type.Namespace}/{type.Name}" : typeAttr.Route;
            var methods = type.GetMethods().ToList();

            var s = methods.Select(p => GenerateMethod(routePrefix, p, serviceName)).ToList();
            s.Insert(0, GenerateConstructorDeclaration(clastName));
            return s.ToArray();
        }

        private static MemberDeclarationSyntax GenerateMethod(string routePrefix, MethodInfo methodInfo, string serviceName)
        {
            if (methodInfo.ReturnType.Namespace != typeof(Task).Namespace)
                throw new NotSupportedException($"Only support proxy asynchronous methods.[{methodInfo.DeclaringType?.Namespace}.{methodInfo.DeclaringType?.Name}.{methodInfo.Name}]");

            var methodAttr = methodInfo.GetCustomAttribute();
            var serviceRoute = $"{routePrefix}/{(methodAttr == null ? methodInfo.Name : methodAttr.Route)}";
            var returnDeclaration = GenerateType(methodInfo.ReturnType);

            var argDeclarations = new List();
            foreach (var arg in methodInfo.GetParameters())
            {
                argDeclarations.Add(arg.ParameterType.IsGenericType
                    ? SyntaxFactory.Parameter(SyntaxFactory.Identifier(arg.Name))
                        .WithType(GenerateType(arg.ParameterType))
                    : SyntaxFactory.Parameter(SyntaxFactory.Identifier(arg.Name))
                        .WithType(GenerateQualifiedNameSyntax(arg.ParameterType)));

                argDeclarations.Add(SyntaxFactory.Token(SyntaxKind.CommaToken));
            }

            if (argDeclarations.Any())
            {
                argDeclarations.RemoveAt(argDeclarations.Count - 1);
            }

            //Generate return type.
            var methodDeclaration = SyntaxFactory.MethodDeclaration(methodInfo.ReturnType == typeof(void) ? SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)) : returnDeclaration, SyntaxFactory.Identifier(methodInfo.Name));

            if (methodInfo.ReturnType.Namespace == typeof(Task).Namespace)
            {
                methodDeclaration = methodDeclaration.WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword),
                    SyntaxFactory.Token(SyntaxKind.AsyncKeyword)));
            }
            else
            {
                methodDeclaration = methodDeclaration.WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword)));
            }

            methodDeclaration = methodDeclaration.WithParameterList(
                SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(argDeclarations)));


            ExpressionSyntax expressionSyntax;

            if (methodInfo.ReturnType == typeof(Task))
            {
                expressionSyntax = SyntaxFactory.IdentifierName("InvokeAsync");
            }
            else
            {
                expressionSyntax = SyntaxFactory.GenericName(SyntaxFactory.Identifier("InvokeAsync"))
                    .WithTypeArgumentList(((GenericNameSyntax)returnDeclaration).TypeArgumentList);
            }

            var argNames = methodInfo.GetParameters().Select(p => SyntaxFactory.IdentifierName(SyntaxFactory.Identifier(p.Name))).ToList();
            var token = new SyntaxNodeOrToken[]
            {
                SyntaxFactory.Argument(SyntaxFactory
                    .ArrayCreationExpression(SyntaxFactory
                        .ArrayType(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)))
                        .WithRankSpecifiers(SyntaxFactory.SingletonList(
                            SyntaxFactory.ArrayRankSpecifier(
                                SyntaxFactory.SingletonSeparatedList(
                                    SyntaxFactory.OmittedArraySizeExpression())))))
                    .WithInitializer(SyntaxFactory.InitializerExpression(SyntaxKind.CollectionInitializerExpression,
                        SyntaxFactory.SeparatedList(argNames)))),

                SyntaxFactory.Token(SyntaxKind.CommaToken),
                SyntaxFactory.Argument(
                    SyntaxFactory.LiteralExpression(
                        SyntaxKind.StringLiteralExpression,
                        SyntaxFactory.Literal(serviceRoute))),
                SyntaxFactory.Token(SyntaxKind.CommaToken),
                SyntaxFactory.Argument(
                    SyntaxFactory.LiteralExpression(
                        SyntaxKind.StringLiteralExpression,
                        SyntaxFactory.Literal(serviceName)))
            };

            expressionSyntax = SyntaxFactory.AwaitExpression(SyntaxFactory.InvocationExpression(expressionSyntax)
                .WithArgumentList(
                    SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(token))
                ));

            StatementSyntax statementSyntax;
            if (methodInfo.ReturnType != typeof(Task) && methodInfo.ReturnType != typeof(void))
            {
                statementSyntax = SyntaxFactory.ReturnStatement(expressionSyntax);
            }
            else
            {
                statementSyntax = SyntaxFactory.ExpressionStatement(expressionSyntax);
            }

            return methodDeclaration.WithBody(SyntaxFactory.Block(SyntaxFactory.SingletonList(statementSyntax)));
        }

        private static ConstructorDeclarationSyntax GenerateConstructorDeclaration(string clastName)
        {
            return SyntaxFactory.ConstructorDeclaration(SyntaxFactory.Identifier(clastName))
                .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
            .AddParameterListParameters(SyntaxFactory.Parameter(SyntaxFactory.Identifier("remotingInvoke"))
                    .WithType(SyntaxFactory.IdentifierName("IRemotingInvoke")))
            .WithInitializer(SyntaxFactory.ConstructorInitializer(SyntaxKind.BaseConstructorInitializer,
                SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(
                    new SyntaxNodeOrToken[]
                    {
                        SyntaxFactory.Argument(SyntaxFactory.IdentifierName("remotingInvoke"))
                    }))))
            .WithBody(SyntaxFactory.Block());
        }
        private static TypeSyntax GenerateType(Type type)
        {
            if (!type.IsGenericType)
                return GenerateQualifiedNameSyntax(type);


            var list = new List();
            foreach (var genericType in type.GetGenericArguments())
            {
                list.Add(genericType.IsGenericType ? GenerateType(genericType) : GenerateQualifiedNameSyntax(genericType.FullName));
                list.Add(SyntaxFactory.Token(SyntaxKind.CommaToken));
            }
            var typeArgumentList = SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(list.Take(list.Count - 1)));
            //if (type.Namespace == typeof(Task).Namespace)
            return SyntaxFactory.GenericName(type.Name.Substring(0, type.Name.IndexOf('`'))).WithTypeArgumentList(typeArgumentList);
            //return SyntaxFactory.GenericName(type.FullName?.Substring(0, type.FullName.IndexOf('`'))).WithTypeArgumentList(typeArgumentList);
        }



        #region QualifiedNameSyntax

        private static QualifiedNameSyntax GenerateQualifiedNameSyntax(Type type)
        {
            return GenerateQualifiedNameSyntax($"{type.Namespace}.{type.Name}");
        }

        private static QualifiedNameSyntax GenerateQualifiedNameSyntax(string fullName)
        {
            return GenerateQualifiedNameSyntax(fullName.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries));
        }

        private static QualifiedNameSyntax GenerateQualifiedNameSyntax(IEnumerable names)
        {
            var identifierNames = names.Select(SyntaxFactory.IdentifierName).ToArray();

            QualifiedNameSyntax left = null;
            for (var i = 0; i < identifierNames.Length - 1; i++)
            {
                left = left == null
                    ? SyntaxFactory.QualifiedName(identifierNames[i], identifierNames[i + 1])
                    : SyntaxFactory.QualifiedName(left, identifierNames[i + 1]);
            }
            return left;
        }
        #endregion


        private static MemoryStream CompileClientProxy(IEnumerable trees, IEnumerable references)
        {
            var astemblies = new[]
            {
                "System.Runtime",
                "mscorlib",
                "System.Threading.Tasks",
                "System.Collections"
            };
            references = astemblies.Select(i => MetadataReference.CreateFromFile(astembly.Load(new astemblyName(i)).Location)).Concat(references);

            references = references.Concat(new[]
            {
                MetadataReference.CreateFromFile(typeof(Task).GetTypeInfo().astembly.Location),
                MetadataReference.CreateFromFile(typeof(DynamicProxyAbstract).GetTypeInfo().astembly.Location)
            });
            return Compile("Uragano.DynamicProxy.UraganoProxy", trees, references);
        }


        private static MemoryStream Compile(string astemblyName, IEnumerable trees, IEnumerable references)
        {
            var compilation = CSharpCompilation.Create(astemblyName, trees, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
            var stream = new MemoryStream();
            var result = compilation.Emit(stream);
            if (!result.Success)
            {
                throw new Exception("Generate dynamic proxy failed:\n" + string.Join(";\n", result.Diagnostics.Select(i => i.ToString())));
            }
            stream.Seek(0, SeekOrigin.Begin);
            return stream;
        }
    }
}