<commit_before>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SerializationConfigurationBase.cs" company="Naos Project">
//    Copyright (c) Naos Project 2019. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Naos.Serialization.Domain
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using OBeautifulCode.Reflection.Recipes;
    using OBeautifulCode.Type;
    using OBeautifulCode.Validation.Recipes;
    using static System.FormattableString;

    /// <summary>
    /// Common configuration base across all kinds of serialization.
    /// </summary>
    public abstract class SerializationConfigurationBase
    {
        /// <summary>
        /// Binding flags used in <see cref="DiscoverAllContainedAssignableTypes"/> to reflect on a type.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Flags", Justification = "Name is correct.")]
        public const BindingFlags DiscoveryBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;

        private readonly object syncConfigure = new object();
        private bool configured;

        private static readonly IReadOnlyCollection<Type> InternallyRequiredTypes = new[]
        {
            typeof(TypeDescription),
            typeof(SerializationDescription),
            typeof(DescribedSerialization),
        };

        /// <summary>
        /// Run configuration logic.
        /// </summary>
        public virtual void Configure()
        {
            if (!this.configured)
            {
                lock (this.syncConfigure)
                {
                    if (!this.configured)
                    {
                        // Save locals to work with.
                        var localClassTypesToRegister = this.ClassTypesToRegister;
                        var localInterfaceTypesToRegisterImplementationOf = this.InterfaceTypesToRegisterImplementationOf ?? new List<Type>();
                        var localTypeToAutoRegisterWithDiscovery = this.TypesToAutoRegisterWithDiscovery ?? new List<Type>();
                        var localTypesToAutoRegister = this.TypesToAutoRegister ?? new List<Type>();
                        var localClassTypesToRegisterAlongWithInheritors = this.ClassTypesToRegisterAlongWithInheritors ?? new List<Type>();

                        // Basic assertions.
                        localInterfaceTypesToRegisterImplementationOf.Must().NotContainAnyNullElements();
                        localTypeToAutoRegisterWithDiscovery.Must().NotContainAnyNullElements();
                        localTypesToAutoRegister.Must().NotContainAnyNullElements();
                        localClassTypesToRegisterAlongWithInheritors.Must().NotContainAnyNullElements();

                        localClassTypesToRegisterAlongWithInheritors.Select(_ => _.IsClass).Named(Invariant($"{nameof(this.ClassTypesToRegisterAlongWithInheritors)}.Select(_ => _.{nameof(Type.IsClass)})")).Must().Each().BeTrue();
                        localInterfaceTypesToRegisterImplementationOf.Select(_ => _.IsInterface).Named(Invariant($"{nameof(this.InterfaceTypesToRegisterImplementationOf)}.Select(_ => _.{nameof(Type.IsInterface)})")).Must().Each().BeTrue();

                        // Run optional initial config.
                        this.InitialConfiguration();

                        // Run all dependent configurations.
                        foreach (var dependentConfigurationType in this.DependentConfigurationTypes ?? new List<Type>())
                        {
                            SerializationConfigurationManager.Configure(dependentConfigurationType);
                        }

                        var discoveredTypes = DiscoverAllContainedAssignableTypes(localTypeToAutoRegisterWithDiscovery);

                        var typesToAutoRegister = new Type[0]
                            .Concat(InternallyRequiredTypes)
                            .Concat(localClassTypesToRegister)
                            .Concat(localTypeToAutoRegisterWithDiscovery)
                            .Concat(localTypesToAutoRegister)
                            .Concat(localInterfaceTypesToRegisterImplementationOf)
                            .Concat(localClassTypesToRegisterAlongWithInheritors)
                            .Concat(discoveredTypes)
                            .Distinct()
                            .ToList();

                        var typesToAutoRegisterWithAssignables = DiscoverAllAssignableTypes(typesToAutoRegister);

                        this.RegisterTypes(typesToAutoRegisterWithAssignables);

                        this.InternalConfigure();

                        this.FinalConfiguration();

                        this.configured = true;
                    }
                }
            }
        }

        private static IReadOnlyCollection<Type> DiscoverAllContainedAssignableTypes(IReadOnlyCollection<Type> types)
        {
            var typeHashSet = new HashSet<Type>();
            var typeSeen = new HashSet<Type>();
            var typesToInspect = new HashSet<Type>(types);

            bool FilterToUsableTypes(MemberInfo memberInfo) => !memberInfo.CustomAttributes.Select(s => s.AttributeType).Contains(typeof(CompilerGeneratedAttribute));

            while (typesToInspect.Any())
            {
                var type = typesToInspect.First();
                typesToInspect.Remove(type);

                if (typeSeen.Contains(type))
                {
                    continue;
                }

                typeSeen.Add(type);

                if (type.IsGenericType)
                {
                    type.GetGenericArguments().ToList().ForEach(_ => typesToInspect.Add(_));
                }

                if (type.Namespace?.StartsWith(nameof(System), StringComparison.Ordinal) ?? true)
                {
                    continue;
                }

                typeHashSet.Add(type);

                var newTypes = type.GetMembers(DiscoveryBindingFlags).Where(FilterToUsableTypes).SelectMany(
                    _ =>
                    {
                        if (_ is PropertyInfo propertyInfo)
                        {
                            return new[] { propertyInfo.PropertyType };
                        }
                        else if (_ is FieldInfo fieldInfo)
                        {
                            return new[] { fieldInfo.FieldType };
                        }
                        else
                        {
                            return new Type[0];
                        }
                    }).Where(_ => !_.IsGenericParameter).ToList();

                newTypes.Distinct().ToList().ForEach(_ => typesToInspect.Add(_));
            }

            var result = typeHashSet.Where(_ => _.IsAssignableType()).ToList();
            return result;
        }

        /// <summary>
        /// Register the specified type and class types that are assignable to those specified types.
        /// </summary>
        /// <param name="types">The types.</param>
        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Want to be used from derivatives using 'this.'")]
        private static IReadOnlyCollection<Type> DiscoverAllAssignableTypes(IReadOnlyCollection<Type> types)
        {
            new { types }.Must().NotBeNull();

            var allTypesToConsiderForRegistration = GetAllTypesToConsiderForRegistration();
            var classTypesToRegister = allTypesToConsiderForRegistration.Where(typeToConsider =>
                        typeToConsider.IsClass &&
                        (!typeToConsider.IsAnonymous()) &&
                        (!typeToConsider
                            .IsGenericTypeDefinition) && // can't do an IsAssignableTo check on generic type definitions
                        types.Any(typeToAutoRegister => typeToConsider.IsAssignableTo(typeToAutoRegister)))
                .Concat(types.Where(_ => _.IsInterface)) // add interfaces back as they were explicitly provided.
                .Distinct()
                .ToList();

            return classTypesToRegister;
        }

        /// <summary>
        /// Discover all types that should considered for registration when looking for derivative types.
        /// </summary>
        /// <returns>All types that should be considered for registration.</returns>
        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Want this to be a method since it's running logic.")]
        protected static IReadOnlyCollection<Type> GetAllTypesToConsiderForRegistration()
        {
            return AssemblyLoader.GetLoadedAssemblies().GetTypesFromAssemblies();
        }

        /// <summary>
        /// Gets a list of <see cref="SerializationConfigurationBase"/>'s that are needed for the current implementation of <see cref="SerializationConfigurationBase"/>.  Optionally overrideable, DEFAULT is empty collection.
        /// </summary>
        protected virtual IReadOnlyCollection<Type> DependentConfigurationTypes => new Type[0];

        /// <summary>
        /// Gets a list of <see cref="Type"/>s to auto-register.
        /// Auto-registration is a convenient way to register types; it accounts for interface implementations and class inheritance when performing the registration.
        /// For interface types, all implementations will be also be registered.  For classes, all inheritors will also be registered.  These additional types do not need to be specified.<commit_msg>Made assignable type discovery more complete in SerializationConfigurationBase.<commit_after>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SerializationConfigurationBase.cs" company="Naos Project">
//    Copyright (c) Naos Project 2019. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Naos.Serialization.Domain
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using OBeautifulCode.Reflection.Recipes;
    using OBeautifulCode.Type;
    using OBeautifulCode.Validation.Recipes;
    using static System.FormattableString;

    /// <summary>
    /// Common configuration base across all kinds of serialization.
    /// </summary>
    public abstract class SerializationConfigurationBase
    {
        /// <summary>
        /// Binding flags used in <see cref="DiscoverAllContainedAssignableTypes"/> to reflect on a type.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Flags", Justification = "Name is correct.")]
        public const BindingFlags DiscoveryBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;

        private readonly object syncConfigure = new object();
        private bool configured;

        private static readonly IReadOnlyCollection<Type> InternallyRequiredTypes = new[]
        {
            typeof(TypeDescription),
            typeof(SerializationDescription),
            typeof(DescribedSerialization),
        };

        /// <summary>
        /// Run configuration logic.
        /// </summary>
        public virtual void Configure()
        {
            if (!this.configured)
            {
                lock (this.syncConfigure)
                {
                    if (!this.configured)
                    {
                        // Save locals to work with.
                        var localClassTypesToRegister = this.ClassTypesToRegister;
                        var localInterfaceTypesToRegisterImplementationOf = this.InterfaceTypesToRegisterImplementationOf ?? new List<Type>();
                        var localTypeToAutoRegisterWithDiscovery = this.TypesToAutoRegisterWithDiscovery ?? new List<Type>();
                        var localTypesToAutoRegister = this.TypesToAutoRegister ?? new List<Type>();
                        var localClassTypesToRegisterAlongWithInheritors = this.ClassTypesToRegisterAlongWithInheritors ?? new List<Type>();

                        // Basic assertions.
                        localInterfaceTypesToRegisterImplementationOf.Must().NotContainAnyNullElements();
                        localTypeToAutoRegisterWithDiscovery.Must().NotContainAnyNullElements();
                        localTypesToAutoRegister.Must().NotContainAnyNullElements();
                        localClassTypesToRegisterAlongWithInheritors.Must().NotContainAnyNullElements();

                        localClassTypesToRegisterAlongWithInheritors.Select(_ => _.IsClass).Named(Invariant($"{nameof(this.ClassTypesToRegisterAlongWithInheritors)}.Select(_ => _.{nameof(Type.IsClass)})")).Must().Each().BeTrue();
                        localInterfaceTypesToRegisterImplementationOf.Select(_ => _.IsInterface).Named(Invariant($"{nameof(this.InterfaceTypesToRegisterImplementationOf)}.Select(_ => _.{nameof(Type.IsInterface)})")).Must().Each().BeTrue();

                        // Run optional initial config.
                        this.InitialConfiguration();

                        // Run all dependent configurations.
                        foreach (var dependentConfigurationType in this.DependentConfigurationTypes ?? new List<Type>())
                        {
                            SerializationConfigurationManager.Configure(dependentConfigurationType);
                        }

                        var discoveredTypes = DiscoverAllContainedAssignableTypes(localTypeToAutoRegisterWithDiscovery);

                        var typesToAutoRegister = new Type[0]
                            .Concat(InternallyRequiredTypes)
                            .Concat(localClassTypesToRegister)
                            .Concat(localTypeToAutoRegisterWithDiscovery)
                            .Concat(localTypesToAutoRegister)
                            .Concat(localInterfaceTypesToRegisterImplementationOf)
                            .Concat(localClassTypesToRegisterAlongWithInheritors)
                            .Concat(discoveredTypes)
                            .Distinct()
                            .ToList();

                        var typesToAutoRegisterWithAssignables = DiscoverAllAssignableTypes(typesToAutoRegister);

                        this.RegisterTypes(typesToAutoRegisterWithAssignables);

                        this.InternalConfigure();

                        this.FinalConfiguration();

                        this.configured = true;
                    }
                }
            }
        }

        private static IReadOnlyCollection<Type> DiscoverAllContainedAssignableTypes(IReadOnlyCollection<Type> types)
        {
            var typeHashSet = new HashSet<Type>();
            var typeSeen = new HashSet<Type>();
            var typesToInspect = new HashSet<Type>(types);

            bool FilterToUsableTypes(MemberInfo memberInfo) => !memberInfo.CustomAttributes.Select(s => s.AttributeType).Contains(typeof(CompilerGeneratedAttribute));

            while (typesToInspect.Any())
            {
                var type = typesToInspect.First();
                typesToInspect.Remove(type);

                if (typeSeen.Contains(type))
                {
                    continue;
                }

                typeSeen.Add(type);

                if (type.IsGenericType)
                {
                    type.GetGenericArguments().ToList().ForEach(_ => typesToInspect.Add(_));
                }

                if (type.Namespace?.StartsWith(nameof(System), StringComparison.Ordinal) ?? true)
                {
                    continue;
                }

                typeHashSet.Add(type);

                var newTypes = type.GetMembers(DiscoveryBindingFlags).Where(FilterToUsableTypes).SelectMany(
                    _ =>
                    {
                        if (_ is PropertyInfo propertyInfo)
                        {
                            return new[] { propertyInfo.PropertyType };
                        }
                        else if (_ is FieldInfo fieldInfo)
                        {
                            return new[] { fieldInfo.FieldType };
                        }
                        else
                        {
                            return new Type[0];
                        }
                    }).Where(_ => !_.IsGenericParameter).ToList();

                newTypes.Distinct().ToList().ForEach(_ => typesToInspect.Add(_));
            }

            var result = typeHashSet.Where(_ => _.IsAssignableType()).ToList();
            return result;
        }

        /// <summary>
        /// Register the specified type and class types that are assignable to those specified types.
        /// </summary>
        /// <param name="types">The types.</param>
        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Want to be used from derivatives using 'this.'")]
        private static IReadOnlyCollection<Type> DiscoverAllAssignableTypes(IReadOnlyCollection<Type> types)
        {
            new { types }.Must().NotBeNull();

            var allTypesToConsiderForRegistration = GetAllTypesToConsiderForRegistration();
            var classTypesToRegister = allTypesToConsiderForRegistration.Where(typeToConsider =>
                        typeToConsider != typeof(object) &&
                        typeToConsider.IsClass &&
                        (!typeToConsider.IsAnonymous()) &&
                        (!typeToConsider
                            .IsGenericTypeDefinition) && // can't do an IsAssignableTo check on generic type definitions
                        types.Any(typeToAutoRegister => typeToConsider.IsAssignableTo(typeToAutoRegister) || typeToAutoRegister.IsAssignableTo(typeToConsider)))
                .Concat(types.Where(_ => _.IsInterface)) // add interfaces back as they were explicitly provided.
                .Distinct()
                .ToList();

            return classTypesToRegister;
        }

        /// <summary>
        /// Discover all types that should considered for registration when looking for derivative types.
        /// </summary>
        /// <returns>All types that should be considered for registration.</returns>
        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Want this to be a method since it's running logic.")]
        protected static IReadOnlyCollection<Type> GetAllTypesToConsiderForRegistration()
        {
            return AssemblyLoader.GetLoadedAssemblies().GetTypesFromAssemblies();
        }

        /// <summary>
        /// Gets a list of <see cref="SerializationConfigurationBase"/>'s that are needed for the current implementation of <see cref="SerializationConfigurationBase"/>.  Optionally overrideable, DEFAULT is empty collection.
        /// </summary>
        protected virtual IReadOnlyCollection<Type> DependentConfigurationTypes => new Type[0];

        /// <summary>
        /// Gets a list of <see cref="Type"/>s to auto-register.
        /// Auto-registration is a convenient way to register types; it accounts for interface implementations and class inheritance when performing the registration.
        /// For interface types, all implementations will be also be registered.  For classes, all inheritors will also be registered.  These additional types do not need to be specified.
{'fraction_non_alphanumeric': 0.10755251005511694, 'fraction_numerical': 0.001886886141317841, 'mean_word_length': 1.771051183269125, 'pattern_counts': {'":': 0, '<': 79, '<?xml version=': 0, '>': 109, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2493996', 'n_tokens_mistral': 4525, 'n_tokens_neox': 4244, 'n_words': 947}