<commit_before>﻿using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Formatting;

namespace NoiseLab.PolyGen.Core.Domain
{
    public sealed class Database
    {
        public string GenerateCodeAsString()
        {
            var compilationUnit = GenerateCompilationUnitSyntax();

            using (var workspace = new AdhocWorkspace())
            {
                var formattedCode = Formatter.Format(compilationUnit, workspace).ToString();
                return formattedCode;
            }
        }

        public CodeGenerationArtifact GenerateCode()
        {
            var compilationUnit = GenerateCompilationUnitSyntax();
            var syntaxTree = CSharpSyntaxTree.Create(compilationUnit);

            MetadataReference[] references =
            {
                MetadataReference.CreateFromFile(CreateMetadataReference("netstandard.library.2.0.0", @"build\netstandard2.0\ref\netstandard.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("netstandard.library.2.0.0", @"build\netstandard2.0\ref\System.Runtime.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("system.componentmodel.annotations.4.4.0", @"lib\netcore50\System.ComponentModel.Annotations.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.entityframeworkcore.2.0.0", @"lib\netstandard2.0\Microsoft.EntityFrameworkCore.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.entityframeworkcore.relational.2.0.0", @"lib\netstandard2.0\Microsoft.EntityFrameworkCore.Relational.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.entityframeworkcore.sqlserver.2.0.0", @"lib\netstandard2.0\Microsoft.EntityFrameworkCore.SqlServer.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.hosting.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Hosting.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.hosting.abstractions.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Hosting.Abstractions.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.http.abstractions.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Http.Abstractions.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.mvc.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Mvc.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.mvc.abstractions.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Mvc.Abstractions.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.mvc.core.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Mvc.Core.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.mvc.viewfeatures.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Mvc.ViewFeatures.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.server.iisintegration.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Server.IISIntegration.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.server.kestrel.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Server.Kestrel.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.extensions.configuration.2.0.0", @"lib\netstandard2.0\Microsoft.Extensions.Configuration.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.extensions.configuration.binder.2.0.0", @"lib\netstandard2.0\Microsoft.Extensions.Configuration.Binder.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.extensions.configuration.abstractions.2.0.0", @"lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.extensions.configuration.environmentvariables.2.0.0", @"lib\netstandard2.0\Microsoft.Extensions.Configuration.EnvironmentVariables.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.extensions.configuration.fileextensions.2.0.0", @"lib\netstandard2.0\Microsoft.Extensions.Configuration.FileExtensions.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.extensions.configuration.json.2.0.0", @"lib\netstandard2.0\Microsoft.Extensions.Configuration.Json.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.extensions.dependencyinjection.abstractions.2.0.0", @"lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll"))
            };

            var compilation = CSharpCompilation.Create(
                "NoiseLab.PolyGen.Generated",
                new[] { syntaxTree },
                references,
                new CSharpCompilationOptions(OutputKind.ConsoleApplication));

            EmitResult result;
            byte[] peBytes = null;
            byte[] pdbBytes = null;
            byte[] xmlBytes = null;
            using (var peStream = new MemoryStream())
            {
                using (var pdbStream = new MemoryStream())
                {
                    using (var xmlStream = new MemoryStream())
                    {
                        result = compilation.Emit(peStream, pdbStream, xmlStream);

                        if (result.Success)
                        {
                            peStream.Seek(0, SeekOrigin.Begin);
                            peBytes = peStream.ToArray();

                            pdbStream.Seek(0, SeekOrigin.Begin);
                            pdbBytes = pdbStream.ToArray();

                            xmlStream.Seek(0, SeekOrigin.Begin);
                            xmlBytes = xmlStream.ToArray();
                        }
                    }
                }
            }
            return new CodeGenerationArtifact(result, peBytes, pdbBytes, xmlBytes);
        }

        private static string CreateMetadataReference(string nugetPackageName, string path)
        {
            // TODO: Is it possible to avoid I/O tasks for code generation?
            var resource = (byte[])Resources.ResourceManager.GetObject(nugetPackageName.Replace('.', '_'));
            using (var memoryStream = new MemoryStream(resource))
            {
                using (var archive = new ZipArchive(memoryStream))
                {
                    var tempPath = Path.Combine(
                        Path.GetTempPath(),
                        $@"NoiseLab.PolyGen\MetadataReferences\{nugetPackageName}");
                    archive.ExtractToDirectory(tempPath, true);
                    return Path.Combine(tempPath, path);
                }
            }
        }

        private CompilationUnitSyntax GenerateCompilationUnitSyntax()
        {
            var classes = new List<MemberDeclarationSyntax>();
            classes.AddRange(Tables.Select(t => t.GenerateOrmModel()));
            classes.AddRange(Tables.Select(t => t.GeneratePostValidationModel()));
            classes.AddRange(Tables.Select(t => t.GeneratePutValidationModel()));
            classes.Add(GenerateDbContext());
            classes.Add(GenerateStartup());
            classes.Add(GenerateProgram());
            classes.AddRange(Tables.Select(table => table.GenerateWebApiController()));
            classes.AddRange(GenerateInfrastructureClasses());

            MemberDeclarationSyntax @namespace = SyntaxFactory
                .NamespaceDeclaration(SyntaxFactory.ParseName("NoiseLab.PolyGen.Generated"))
                .AddUsings(
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.IO")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Linq")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Collections.Generic")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.ComponentModel.DataAnnotations")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.ComponentModel.DataAnnotations.Schema")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Threading.Tasks")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.EntityFrameworkCore")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.AspNetCore.Mvc")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.AspNetCore.Mvc.Filters")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.AspNetCore.Builder")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.AspNetCore.Hosting")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.Extensions.Configuration")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.Extensions.DependencyInjection"))
                )
                .AddMembers(classes.ToArray());

            return SyntaxFactory.CompilationUnit().AddMembers(@namespace);
        }

        internal Database(IReadOnlyList<Table> tables, IEnumerable<Relationship> relationships)
        {
            Tables = tables;
            Relationships = relationships;
        }

        internal IReadOnlyList<Table> Tables { get; }

        internal IEnumerable<Relationship> Relationships { get; }

        private static IEnumerable<ClassDeclarationSyntax> GenerateInfrastructureClasses()
        {
            var classes = new List<ClassDeclarationSyntax>();

            classes.Add(GenerateModelStateValidationAttribute());
            classes.Add(GenerateNullValidationAttribute());

            return classes;
        }

        private static ClassDeclarationSyntax GenerateModelStateValidationAttribute()
        {
            var members = new List<MemberDeclarationSyntax>();

            var configureServicesMethod = SyntaxFactory
                .MethodDeclaration(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)),
                    "OnActionExecuting")
                .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
                .AddModifiers(SyntaxFactory.Token(SyntaxKind.OverrideKeyword))
                .AddParameterListParameters(
                    SyntaxFactory
                        .Parameter(SyntaxFactory.Identifier("context"))
                        .WithType(SyntaxFactory.ParseTypeName("ActionExecutingContext")))
                .AddBodyStatements(
                    SyntaxFactory.ParseStatement(
                        "if (!context.ModelState.IsValid) { context.Result = new BadRequestObjectResult(context.ModelState); }"),
                    SyntaxFactory.ParseStatement("base.OnActionExecuting(context);"));
            members.Add(configureServicesMethod);
<commit_msg>Minor changes<commit_after>﻿using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Formatting;

namespace NoiseLab.PolyGen.Core.Domain
{
    public sealed class Database
    {
        public string GenerateCodeAsString()
        {
            var compilationUnit = GenerateCompilationUnitSyntax();

            using (var workspace = new AdhocWorkspace())
            {
                var formattedCode = Formatter.Format(compilationUnit, workspace).ToString();
                return formattedCode;
            }
        }

        public CodeGenerationArtifact GenerateCode()
        {
            var compilationUnit = GenerateCompilationUnitSyntax();
            var syntaxTree = CSharpSyntaxTree.Create(compilationUnit);

            MetadataReference[] references =
            {
                MetadataReference.CreateFromFile(CreateMetadataReference("netstandard.library.2.0.0", @"build\netstandard2.0\ref\netstandard.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("netstandard.library.2.0.0", @"build\netstandard2.0\ref\System.Runtime.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("system.componentmodel.annotations.4.4.0", @"lib\netcore50\System.ComponentModel.Annotations.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.entityframeworkcore.2.0.0", @"lib\netstandard2.0\Microsoft.EntityFrameworkCore.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.entityframeworkcore.relational.2.0.0", @"lib\netstandard2.0\Microsoft.EntityFrameworkCore.Relational.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.entityframeworkcore.sqlserver.2.0.0", @"lib\netstandard2.0\Microsoft.EntityFrameworkCore.SqlServer.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.hosting.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Hosting.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.hosting.abstractions.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Hosting.Abstractions.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.http.abstractions.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Http.Abstractions.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.mvc.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Mvc.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.mvc.abstractions.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Mvc.Abstractions.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.mvc.core.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Mvc.Core.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.mvc.viewfeatures.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Mvc.ViewFeatures.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.server.iisintegration.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Server.IISIntegration.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.aspnetcore.server.kestrel.2.0.0", @"lib\netstandard2.0\Microsoft.AspNetCore.Server.Kestrel.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.extensions.configuration.2.0.0", @"lib\netstandard2.0\Microsoft.Extensions.Configuration.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.extensions.configuration.binder.2.0.0", @"lib\netstandard2.0\Microsoft.Extensions.Configuration.Binder.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.extensions.configuration.abstractions.2.0.0", @"lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.extensions.configuration.environmentvariables.2.0.0", @"lib\netstandard2.0\Microsoft.Extensions.Configuration.EnvironmentVariables.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.extensions.configuration.fileextensions.2.0.0", @"lib\netstandard2.0\Microsoft.Extensions.Configuration.FileExtensions.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.extensions.configuration.json.2.0.0", @"lib\netstandard2.0\Microsoft.Extensions.Configuration.Json.dll")),
                MetadataReference.CreateFromFile(CreateMetadataReference("microsoft.extensions.dependencyinjection.abstractions.2.0.0", @"lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll"))
            };

            var compilation = CSharpCompilation.Create(
                "NoiseLab.PolyGen.Generated",
                new[] { syntaxTree },
                references,
                new CSharpCompilationOptions(OutputKind.ConsoleApplication));

            EmitResult result;
            byte[] peBytes = null;
            byte[] pdbBytes = null;
            byte[] xmlBytes = null;
            using (var peStream = new MemoryStream())
            {
                using (var pdbStream = new MemoryStream())
                {
                    using (var xmlStream = new MemoryStream())
                    {
                        result = compilation.Emit(peStream, pdbStream, xmlStream);

                        if (result.Success)
                        {
                            peStream.Seek(0, SeekOrigin.Begin);
                            peBytes = peStream.ToArray();

                            pdbStream.Seek(0, SeekOrigin.Begin);
                            pdbBytes = pdbStream.ToArray();

                            xmlStream.Seek(0, SeekOrigin.Begin);
                            xmlBytes = xmlStream.ToArray();
                        }
                    }
                }
            }
            return new CodeGenerationArtifact(result, peBytes, pdbBytes, xmlBytes);
        }

        private static string CreateMetadataReference(string nugetPackageName, string path)
        {
            // TODO: Is it possible to avoid disk I/O for code generation?
            var resource = (byte[])Resources.ResourceManager.GetObject(nugetPackageName.Replace('.', '_'));
            using (var memoryStream = new MemoryStream(resource))
            {
                using (var archive = new ZipArchive(memoryStream))
                {
                    var tempPath = Path.Combine(
                        Path.GetTempPath(),
                        $@"NoiseLab.PolyGen\MetadataReferences\{nugetPackageName}");
                    archive.ExtractToDirectory(tempPath, true);
                    return Path.Combine(tempPath, path);
                }
            }
        }

        private CompilationUnitSyntax GenerateCompilationUnitSyntax()
        {
            var classes = new List<MemberDeclarationSyntax>();
            classes.AddRange(Tables.Select(t => t.GenerateOrmModel()));
            classes.AddRange(Tables.Select(t => t.GeneratePostValidationModel()));
            classes.AddRange(Tables.Select(t => t.GeneratePutValidationModel()));
            classes.Add(GenerateDbContext());
            classes.Add(GenerateStartup());
            classes.Add(GenerateProgram());
            classes.AddRange(Tables.Select(table => table.GenerateWebApiController()));
            classes.AddRange(GenerateInfrastructureClasses());

            MemberDeclarationSyntax @namespace = SyntaxFactory
                .NamespaceDeclaration(SyntaxFactory.ParseName("NoiseLab.PolyGen.Generated"))
                .AddUsings(
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.IO")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Linq")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Collections.Generic")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.ComponentModel.DataAnnotations")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.ComponentModel.DataAnnotations.Schema")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Threading.Tasks")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.EntityFrameworkCore")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.AspNetCore.Mvc")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.AspNetCore.Mvc.Filters")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.AspNetCore.Builder")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.AspNetCore.Hosting")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.Extensions.Configuration")),
                    SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.Extensions.DependencyInjection"))
                )
                .AddMembers(classes.ToArray());

            return SyntaxFactory.CompilationUnit().AddMembers(@namespace);
        }

        internal Database(IReadOnlyList<Table> tables, IEnumerable<Relationship> relationships)
        {
            Tables = tables;
            Relationships = relationships;
        }

        internal IReadOnlyList<Table> Tables { get; }

        internal IEnumerable<Relationship> Relationships { get; }

        private static IEnumerable<ClassDeclarationSyntax> GenerateInfrastructureClasses()
        {
            var classes = new List<ClassDeclarationSyntax>
            {
                GenerateModelStateValidationAttribute(),
                GenerateNullValidationAttribute()
            };

            return classes;
        }

        private static ClassDeclarationSyntax GenerateModelStateValidationAttribute()
        {
            var members = new List<MemberDeclarationSyntax>();

            var configureServicesMethod = SyntaxFactory
                .MethodDeclaration(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)),
                    "OnActionExecuting")
                .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
                .AddModifiers(SyntaxFactory.Token(SyntaxKind.OverrideKeyword))
                .AddParameterListParameters(
                    SyntaxFactory
                        .Parameter(SyntaxFactory.Identifier("context"))
                        .WithType(SyntaxFactory.ParseTypeName("ActionExecutingContext")))
                .AddBodyStatements(
                    SyntaxFactory.ParseStatement(
                        "if (!context.ModelState.IsValid) { context.Result = new BadRequestObjectResult(context.ModelState); }"),
                    SyntaxFactory.ParseStatement("base.OnActionExecuting(context);"));
            members.Add(configureServicesMethod);

{'fraction_non_alphanumeric': 0.10257874621095638, 'fraction_numerical': 0.009928392566884856, 'mean_word_length': 2.702065376483981, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 14}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '815190', 'n_tokens_mistral': 5714, 'n_tokens_neox': 5370, 'n_words': 587}