<commit_before>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DataContractSerializerFactory.cs" company="Catel development team">
//   Copyright (c) 2008 - 2013 Catel development team. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Catel.Runtime.Serialization
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.Serialization;
    using Catel.Caching;
    using Catel.Data;
    using Catel.Logging;
    using Catel.Reflection;

    /// <summary>
    /// Default implementation of the <see cref="IDataContractSerializerFactory" /> interface.
    /// </summary>
    public class DataContractSerializerFactory : IDataContractSerializerFactory
    {
        #region Fields
        /// <summary>
        /// The <see cref="ILog">log</see> object.
        /// </summary>
        private static readonly ILog Log = LogManager.GetCurrentClassLogger();

        /// <summary>
        /// Cache for the <see cref="DataContractSerializer"/> per name.
        /// </summary>
        private readonly Dictionary<string, DataContractSerializer> _dataContractSerializers = new Dictionary<string, DataContractSerializer>();

        /// <summary>
        /// Cache for known attributes per type.
        /// </summary>
        private readonly Dictionary<string, Type[]> _knownTypesByAttributesCache = new Dictionary<string, Type[]>();

        private readonly IDataContractNameResolver _dataContractNameResolver;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="DataContractSerializerFactory"/> class.
        /// </summary>
        /// <param name="dataContractNameResolver">The data contract name resolver.</param>
        public DataContractSerializerFactory(IDataContractNameResolver dataContractNameResolver)
        {
            Argument.IsNotNull(() => dataContractNameResolver);

            _dataContractNameResolver = dataContractNameResolver;
        }
        #endregion

        #region IDataContractSerializerFactory Members
        /// <summary>
        /// Gets the Data Contract serializer for a specific type. This method caches serializers so the
        /// performance can be improved when a serializer is used more than once.
        /// </summary>
        /// <param name="serializingType">The type that is currently (de)serializing.</param>
        /// <param name="typeToSerialize">The type to (de)serialize.</param>
        /// <param name="xmlName">Name of the property as known in XML.</param>
        /// <param name="serializingObject">The object to create the serializer for. When the object is not <c>null</c>, the types that are
        /// a child object of this object are added to the known types of the serializer.</param>
        /// <param name="additionalKnownTypes">A list of additional types to add to the known types.</param>
        /// <returns>
        /// <see cref="DataContractSerializer"/> for the given type.
        /// </returns>
        /// <exception cref="ArgumentNullException">The <paramref name="serializingType"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentNullException">The <paramref name="typeToSerialize"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentException">The <paramref name="xmlName"/> is <c>null</c> or whitespace.</exception>
        public virtual DataContractSerializer GetDataContractSerializer(Type serializingType, Type typeToSerialize, string xmlName, object serializingObject = null, List<Type> additionalKnownTypes = null)
        {
            Argument.IsNotNull("serializingType", serializingType);
            Argument.IsNotNull("typeToSerialize", typeToSerialize);
            Argument.IsNotNullOrWhitespace("xmlName", xmlName);

            string key = string.Format("{0}|{1}", typeToSerialize.GetSafeFullName(), xmlName);
            if (_dataContractSerializers.ContainsKey(key))
            {
                return _dataContractSerializers[key];
            }

            Log.Debug("Getting known types for xml serialization of '{0}'", typeToSerialize.GetSafeFullName());

            var serializerTypeInfo = new XmlSerializerTypeInfo(serializingType, typeToSerialize, additionalKnownTypes);

            if (serializingObject != null)
            {
                GetKnownTypesForInstance(serializingObject, serializerTypeInfo);
            }
            else
            {
                GetKnownTypes(typeToSerialize, serializerTypeInfo);
            }

            var knownTypesViaAttributes = GetKnownTypesViaAttributes(serializingType);
            foreach (var knownTypeViaAttribute in knownTypesViaAttributes)
            {
                GetKnownTypes(knownTypeViaAttribute, serializerTypeInfo);
            }

            if (additionalKnownTypes != null)
            {
                foreach (var additionalKnownType in additionalKnownTypes)
                {
                    GetKnownTypes(additionalKnownType, serializerTypeInfo);
                }
            }

            var xmlSerializer = new DataContractSerializer(typeToSerialize, xmlName, string.Empty, serializerTypeInfo.KnownTypes);

            if (!string.IsNullOrEmpty(key))
            {
                _dataContractSerializers[key] = xmlSerializer;
            }

            return xmlSerializer;
        }
        #endregion

        #region Methods
        /// <summary>
        /// Gets the known types for a specific object instance.
        /// </summary>
        /// <param name="obj">The object to retrieve the known types for.</param>
        /// <param name="serializerTypeInfo">The serializer type info.</param>
        /// <returns>Array of <see cref="Type"/> that are found in the object instance.</returns>
        protected virtual void GetKnownTypesForInstance(object obj, XmlSerializerTypeInfo serializerTypeInfo)
        {
            if (obj == null)
            {
                return;
            }

            Type objectType = obj.GetType();

            if (!ShouldTypeBeHandled(objectType, serializerTypeInfo))
            {
                return;
            }

            Log.Debug("Getting known types for instance of '{0}'", objectType.GetSafeFullName());

            GetKnownTypes(objectType, serializerTypeInfo);

            if (objectType.FullName == null)
            {
                serializerTypeInfo.AddTypeAsHandled(objectType);
                return;
            }

            if (objectType == typeof(List<KeyValuePair<string, object>>))
            {
                foreach (var keyValuePair in ((List<KeyValuePair<string, object>>)obj))
                {
                    GetKnownTypesForInstance(keyValuePair.Value, serializerTypeInfo);
                }
            }
            else if (objectType == typeof(List<PropertyValue>))
            {
                foreach (var propertyValue in ((List<PropertyValue>)obj))
                {
                    GetKnownTypesForInstance(propertyValue.Value, serializerTypeInfo);
                }
            }

            // Collections might contain interface types, so if this is an IEnumerable, we need to loop all the instances (performance warning!)
            else if ((obj is IEnumerable) && (!(obj is string)))
            {
                var objAsIEnumerable = (IEnumerable)obj;
                foreach (object item in objAsIEnumerable)
                {
                    if (item != null)
                    {
                        GetKnownTypesForInstance(item, serializerTypeInfo);
                    }
                }
            }

            // Generic collections are special in Silverlight and WP7 (WHY?!)
            else if (serializerTypeInfo.IsSpecialCollectionType(objectType) && !objectType.IsInterfaceEx())
            {
                AddTypeToKnownTypesIfSerializable(objectType, serializerTypeInfo);
            }
            else if (!objectType.GetSafeFullName().StartsWith("System."))
            {
                bool allowNonPublicReflection = AllowNonPublicReflection(objectType);

                var fields = objectType.GetFieldsEx(BindingFlagsHelper.GetFinalBindingFlags(false, false, allowNonPublicReflection));
                foreach (var field in fields)
                {
                    try
                    {
                        object value = field.GetValue(obj);
                        GetKnownTypes(value == null ? field.FieldType : value.GetType(), serializerTypeInfo);
                    }
                    catch (Exception)
                    {
                        Log.Warning("Failed to get value for field '{0}' of type '{1}'", field.Name, objectType.GetSafeFullName());
                    }
                }

                var properties = objectType.GetPropertiesEx(BindingFlagsHelper.GetFinalBindingFlags(false, false, allowNonPublicReflection));
                foreach (var property in properties)
                {
                    try
                    {
                        object value = property.GetValue(obj, null);
                        GetKnownTypes(value == null ? property.PropertyType : value.GetType(), serializerTypeInfo);
                    }
                    catch (Exception)
                    {
                        Log.Warning("Failed to get value for property '{0}' of type '{1}'", property.Name, objectType.GetSafeFullName());
                    }
                }
            }
        }

        /// <summary>
        /// Gets the known types inside the specific type.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="serializerTypeInfo">The serializer type info.</param>
        /// <returns>Array of <see cref="Type"/> that are found in the object type.</returns>
        protected virtual void GetKnownTypes(Type type, XmlSerializerTypeInfo serializerTypeInfo)
        {
            if (!ShouldTypeBeHandled(type, serializerTypeInfo))
            {
                return;
            }

            Log.Debug("Getting known types for '{0}'", type.GetSafeFullName());

            // If this is an interface, HOUSTON, WE HAVE A PROBLEM
            if (type.IsInterfaceEx())
            {
                if (!serializerTypeInfo.IsTypeAlreadyHandled(type))
                {
                    Log.Debug("Type is an interface, checking all types deriving from this interface");

                    // Don't check this interface again in children checks
                    serializerTypeInfo.AddTypeAsHandled(type);

                    // Interfaces are not a type, and in fact a LOT of types can be added (in fact every object implementing the interface). For
                    // serialization, this is not a problem (we know the exact type), but for deserialization this IS an issue because we should
                    // expect EVERY type that implements the type in the whole AppDomain.
                    // This is huge performance hit, but it's the cost for dynamic easy on-the-fly serialization in WPF and Silverlight. Luckily
                    // we already implemented caching.
                    var typesDerivingFromInterface = TypeCache.GetTypes(t => t.ImplementsInterfaceEx(type));
                    foreach (var typeDerivingFromInterface in typesDerivingFromInterface)
                    {
                        if (typeDerivingFromInterface != type)
                        {
                            GetKnownTypes(typeDerivingFromInterface, serializerTypeInfo);
                        }
                    }

                    Log.Debug("Finished checking all types deriving from this interface");
                }

                // The interface itself is ignored
                return;
            }

            if (serializerTypeInfo.IsSpecialCollectionType(type) && !type.IsInterfaceEx())
            {
                Log.Debug("Type is a special collection type, adding it to the array of known types");

                AddTypeToKnownTypesIfSerializable(type, serializerTypeInfo);
            }

            // Fix generics
            if (type.GetSafeFullName().StartsWith("System."))
            {
                var genericArguments = type.GetGenericArgumentsEx();
                foreach (var genericArgument in genericArguments)
                {
                    Log.Debug("Retrieving known types for generic argument '{0}' of '{1}'", genericArgument.GetSafeFullName(), type.GetSafeFullName());

                    GetKnownTypes(genericArgument, serializerTypeInfo);
                }

                return;
            }

            if (!AddTypeToKnownTypesIfSerializable(type, serializerTypeInfo))
            {
                serializerTypeInfo.AddTypeAsHandled(type);
            }

            var isModelBase = (type == typeof (ModelBase)) || typeof (ModelBase).IsAssignableFromEx(type);
            if (isModelBase)
            {
                var modelBaseProperties = PropertyDataManager.Default.GetProperties(type);
                foreach (var modelBaseProperty in modelBaseProperties)
                {
                    var propertyType = modelBaseProperty.Value.Type;
                    if (propertyType.FullName != null)
                    {
                        GetKnownTypes(propertyType, serializerTypeInfo);
                    }
                    else
                    {
                        serializerTypeInfo.AddTypeAsHandled(propertyType);
                    }
                }
            }
            else
            {
                bool allowNonPublicReflection = AllowNonPublicReflection(type);
                
                // Fields
                var fields = type.GetFieldsEx(BindingFlagsHelper.GetFinalBindingFlags(false, false, allowNonPublicReflection));
                foreach (var field in fields)
                {
                    var fieldType = field.FieldType;
                    if (fieldType.FullName != null)
                    {
                        GetKnownTypes(fieldType, serializerTypeInfo);
                    }
                    else
                    {
                        serializerTypeInfo.AddTypeAsHandled(fieldType);
                    }
                }

                // Properties
                var properties = type.GetPropertiesEx(BindingFlagsHelper.GetFinalBindingFlags(false, false, allowNonPublicReflection));
                foreach (var property in properties)
                {
                    var propertyType = property.PropertyType;
                    if (propertyType.FullName != null)
                    {
                        GetKnownTypes(propertyType, serializerTypeInfo);
                    }
                    else
                    {
                        serializerTypeInfo.AddTypeAsHandled(propertyType);
                    }
                }
            }

            // If this isn't the base type, check that as well
            var baseType = type.GetBaseTypeEx();
            if (baseType != null)
            {
                Log.Debug("Checking base type of '{0}' for known types", type.GetSafeFullName());

                if (baseType.FullName != null)
                {
                    GetKnownTypes(baseType, serializerTypeInfo);
                }
                else
                {
                    serializerTypeInfo.AddTypeAsHandled(baseType);
                }
            }

            // Last but not least, check if the type is decorated with KnownTypeAttributes
            var knowTypesByAttributes = GetKnownTypesViaAttributes(type);
            if (knowTypesByAttributes.Length > 0)
            {
                Log.Debug("Found {0} additional known types for type '{1}'", knowTypesByAttributes.Length, type.GetSafeFullName());

                foreach (var knownTypeByAttribute in knowTypesByAttributes)
                {
                    var attributeType = knownTypeByAttribute;
                    if (attributeType.FullName != null)
                    {
                        GetKnownTypes(knownTypeByAttribute, serializerTypeInfo);
                    }
                    else
                    {
                        serializerTypeInfo.AddTypeAsHandled(attributeType);
                    }
                }
            }
        }

        /// <summary>
        /// Determines whether the type should be handled.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="serializerTypeInfo">The serializer type info.</param>
        /// <returns><c>true</c> if the type should be handled; otherwise, <c>false</c>.</returns>
        protected virtual bool ShouldTypeBeHandled(Type type, XmlSerializerTypeInfo serializerTypeInfo)
        {
            if (type == null)
            {
                return false;
            }

            // Note, although resharper says this isn't possible, it might be
            if (type.FullName == null)
            {
                serializerTypeInfo.AddTypeAsHandled(type);
                return false;
            }

            // Ignore non-generic .NET
            if (!type.IsGenericTypeEx() && type.GetSafeFullName().StartsWith("System."))
            {
                // Log.Debug("Non-generic .NET system type, can be ignored");
                serializerTypeInfo.AddTypeAsHandled(type);
                return false;
            }

            return !serializerTypeInfo.ContainsKnownType(type) && !serializerTypeInfo.IsTypeAlreadyHandled(type);
        }

        /// <summary>
        /// Gets the known types via attributes.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>The list of known types via the <see cref="KnownTypeAttribute"/>.</returns>
        /// <exception cref="ArgumentNullException">The <paramref name="type"/> is <c>null</c>.</exception>
        protected virtual Type[] GetKnownTypesViaAttributes(Type type)
        {
            Argument.IsNotNull("type", type);

            string typeName = type.AssemblyQualifiedName;
            lock (_knownTypesByAttributesCache)
            {
                if (!_knownTypesByAttributesCache.ContainsKey(typeName))
                {
                    var additionalTypes = new List<Type>();<commit_msg>Last fix for CTL-4, all unit tests now succeed<commit_after>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DataContractSerializerFactory.cs" company="Catel development team">
//   Copyright (c) 2008 - 2013 Catel development team. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Catel.Runtime.Serialization
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.Serialization;
    using Catel.Caching;
    using Catel.Data;
    using Catel.Logging;
    using Catel.Reflection;

    /// <summary>
    /// Default implementation of the <see cref="IDataContractSerializerFactory" /> interface.
    /// </summary>
    public class DataContractSerializerFactory : IDataContractSerializerFactory
    {
        #region Fields
        /// <summary>
        /// The <see cref="ILog">log</see> object.
        /// </summary>
        private static readonly ILog Log = LogManager.GetCurrentClassLogger();

        /// <summary>
        /// Cache for the <see cref="DataContractSerializer"/> per name.
        /// </summary>
        private readonly Dictionary<string, DataContractSerializer> _dataContractSerializers = new Dictionary<string, DataContractSerializer>();

        /// <summary>
        /// Cache for known attributes per type.
        /// </summary>
        private readonly Dictionary<string, Type[]> _knownTypesByAttributesCache = new Dictionary<string, Type[]>();

        private readonly IDataContractNameResolver _dataContractNameResolver;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="DataContractSerializerFactory"/> class.
        /// </summary>
        /// <param name="dataContractNameResolver">The data contract name resolver.</param>
        public DataContractSerializerFactory(IDataContractNameResolver dataContractNameResolver)
        {
            Argument.IsNotNull(() => dataContractNameResolver);

            _dataContractNameResolver = dataContractNameResolver;
        }
        #endregion

        #region IDataContractSerializerFactory Members
        /// <summary>
        /// Gets the Data Contract serializer for a specific type. This method caches serializers so the
        /// performance can be improved when a serializer is used more than once.
        /// </summary>
        /// <param name="serializingType">The type that is currently (de)serializing.</param>
        /// <param name="typeToSerialize">The type to (de)serialize.</param>
        /// <param name="xmlName">Name of the property as known in XML.</param>
        /// <param name="serializingObject">The object to create the serializer for. When the object is not <c>null</c>, the types that are
        /// a child object of this object are added to the known types of the serializer.</param>
        /// <param name="additionalKnownTypes">A list of additional types to add to the known types.</param>
        /// <returns>
        /// <see cref="DataContractSerializer"/> for the given type.
        /// </returns>
        /// <exception cref="ArgumentNullException">The <paramref name="serializingType"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentNullException">The <paramref name="typeToSerialize"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentException">The <paramref name="xmlName"/> is <c>null</c> or whitespace.</exception>
        public virtual DataContractSerializer GetDataContractSerializer(Type serializingType, Type typeToSerialize, string xmlName, object serializingObject = null, List<Type> additionalKnownTypes = null)
        {
            Argument.IsNotNull("serializingType", serializingType);
            Argument.IsNotNull("typeToSerialize", typeToSerialize);
            Argument.IsNotNullOrWhitespace("xmlName", xmlName);

            string key = string.Format("{0}|{1}", typeToSerialize.GetSafeFullName(), xmlName);
            if (_dataContractSerializers.ContainsKey(key))
            {
                return _dataContractSerializers[key];
            }

            Log.Debug("Getting known types for xml serialization of '{0}'", typeToSerialize.GetSafeFullName());

            var serializerTypeInfo = new XmlSerializerTypeInfo(serializingType, typeToSerialize, additionalKnownTypes);

            if (serializingObject != null)
            {
                GetKnownTypesForInstance(serializingObject, serializerTypeInfo);
            }
            else
            {
                GetKnownTypes(typeToSerialize, serializerTypeInfo);
            }

            var knownTypesViaAttributes = GetKnownTypesViaAttributes(serializingType);
            foreach (var knownTypeViaAttribute in knownTypesViaAttributes)
            {
                GetKnownTypes(knownTypeViaAttribute, serializerTypeInfo);
            }

            if (additionalKnownTypes != null)
            {
                foreach (var additionalKnownType in additionalKnownTypes)
                {
                    GetKnownTypes(additionalKnownType, serializerTypeInfo);
                }
            }

            var xmlSerializer = new DataContractSerializer(typeToSerialize, xmlName, string.Empty, serializerTypeInfo.KnownTypes);

            if (!string.IsNullOrEmpty(key))
            {
                _dataContractSerializers[key] = xmlSerializer;
            }

            return xmlSerializer;
        }
        #endregion

        #region Methods
        /// <summary>
        /// Gets the known types for a specific object instance.
        /// </summary>
        /// <param name="obj">The object to retrieve the known types for.</param>
        /// <param name="serializerTypeInfo">The serializer type info.</param>
        /// <returns>Array of <see cref="Type"/> that are found in the object instance.</returns>
        protected virtual void GetKnownTypesForInstance(object obj, XmlSerializerTypeInfo serializerTypeInfo)
        {
            if (obj == null)
            {
                return;
            }

            Type objectType = obj.GetType();

            if (ShouldTypeBeIgnored(objectType, serializerTypeInfo))
            {
                return;
            }

            GetKnownTypes(objectType, serializerTypeInfo);

            // Note: the code below is specific for instants of objects, cannot be moved to the GetKnownTypes

            if (objectType == typeof(List<KeyValuePair<string, object>>))
            {
                foreach (var keyValuePair in ((List<KeyValuePair<string, object>>)obj))
                {
                    GetKnownTypesForInstance(keyValuePair.Value, serializerTypeInfo);
                }
            }
            else if (objectType == typeof(List<PropertyValue>))
            {
                foreach (var propertyValue in ((List<PropertyValue>)obj))
                {
                    GetKnownTypesForInstance(propertyValue.Value, serializerTypeInfo);
                }
            }

            // Collections might contain interface types, so if this is an IEnumerable, we need to loop all the instances (performance warning!)
            else if ((obj is IEnumerable) && (!(obj is string)))
            {
                var objAsIEnumerable = (IEnumerable)obj;
                foreach (object item in objAsIEnumerable)
                {
                    if (item != null)
                    {
                        GetKnownTypesForInstance(item, serializerTypeInfo);
                    }
                }
            }
        }

        /// <summary>
        /// Gets the known types inside the specific type.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="serializerTypeInfo">The serializer type info.</param>
        /// <returns>Array of <see cref="Type"/> that are found in the object type.</returns>
        protected virtual void GetKnownTypes(Type type, XmlSerializerTypeInfo serializerTypeInfo)
        {
            if (ShouldTypeBeIgnored(type, serializerTypeInfo))
            {
                return;
            }

            Log.Debug("Getting known types for '{0}'", type.GetSafeFullName());

            // If this is an interface, HOUSTON, WE HAVE A PROBLEM
            if (type.IsInterfaceEx())
            {
                if (!serializerTypeInfo.IsTypeAlreadyHandled(type))
                {
                    Log.Debug("Type is an interface, checking all types deriving from this interface");

                    // Don't check this interface again in children checks
                    serializerTypeInfo.AddTypeAsHandled(type);

                    // Interfaces are not a type, and in fact a LOT of types can be added (in fact every object implementing the interface). For
                    // serialization, this is not a problem (we know the exact type), but for deserialization this IS an issue because we should
                    // expect EVERY type that implements the type in the whole AppDomain.
                    // This is huge performance hit, but it's the cost for dynamic easy on-the-fly serialization in WPF and Silverlight. Luckily
                    // we already implemented caching.
                    var typesDerivingFromInterface = TypeCache.GetTypes(t => t.ImplementsInterfaceEx(type));
                    foreach (var typeDerivingFromInterface in typesDerivingFromInterface)
                    {
                        if (typeDerivingFromInterface != type)
                        {
                            GetKnownTypes(typeDerivingFromInterface, serializerTypeInfo);
                        }
                    }

                    Log.Debug("Finished checking all types deriving from this interface");
                }

                // The interface itself is ignored
                return;
            }

            if (serializerTypeInfo.IsSpecialCollectionType(type) && !type.IsInterfaceEx())
            {
                Log.Debug("Type is a special collection type, adding it to the array of known types");

                AddTypeToKnownTypesIfSerializable(type, serializerTypeInfo);
            }

            // Fix generics
            if (type.GetSafeFullName().StartsWith("System."))
            {
                var genericArguments = type.GetGenericArgumentsEx();
                foreach (var genericArgument in genericArguments)
                {
                    Log.Debug("Retrieving known types for generic argument '{0}' of '{1}'", genericArgument.GetSafeFullName(), type.GetSafeFullName());

                    GetKnownTypes(genericArgument, serializerTypeInfo);
                }

                return;
            }

            if (!AddTypeToKnownTypesIfSerializable(type, serializerTypeInfo))
            {
                serializerTypeInfo.AddTypeAsHandled(type);
            }

            AddTypeMembers(type, serializerTypeInfo);

            // If this isn't the base type, check that as well
            var baseType = type.GetBaseTypeEx();
            if (baseType != null)
            {
                Log.Debug("Checking base type of '{0}' for known types", type.GetSafeFullName());

                if (baseType.FullName != null)
                {
                    GetKnownTypes(baseType, serializerTypeInfo);
                }
                else
                {
                    serializerTypeInfo.AddTypeAsHandled(baseType);
                }
            }

            // Last but not least, check if the type is decorated with KnownTypeAttributes
            var knowTypesByAttributes = GetKnownTypesViaAttributes(type);
            if (knowTypesByAttributes.Length > 0)
            {
                Log.Debug("Found {0} additional known types for type '{1}'", knowTypesByAttributes.Length, type.GetSafeFullName());

                foreach (var knownTypeByAttribute in knowTypesByAttributes)
                {
                    var attributeType = knownTypeByAttribute;
                    if (attributeType.FullName != null)
                    {
                        GetKnownTypes(knownTypeByAttribute, serializerTypeInfo);
                    }
                    else
                    {
                        serializerTypeInfo.AddTypeAsHandled(attributeType);
                    }
                }
            }
        }

        private void AddTypeMembers(Type type, XmlSerializerTypeInfo serializerTypeInfo)
        {
            var isModelBase = (type == typeof(ModelBase)) || typeof(ModelBase).IsAssignableFromEx(type);
            if (isModelBase)
            {
                var modelBaseProperties = PropertyDataManager.Default.GetProperties(type);
                foreach (var modelBaseProperty in modelBaseProperties)
                {
                    var propertyType = modelBaseProperty.Value.Type;
                    if (propertyType.FullName != null)
                    {
                        GetKnownTypes(propertyType, serializerTypeInfo);
                    }
                    else
                    {
                        serializerTypeInfo.AddTypeAsHandled(propertyType);
                    }
                }
            }
            else
            {
                bool allowNonPublicReflection = AllowNonPublicReflection(type);

                // Fields
                var fields = type.GetFieldsEx(BindingFlagsHelper.GetFinalBindingFlags(false, false, allowNonPublicReflection));
                foreach (var field in fields)
                {
                    var fieldType = field.FieldType;
                    if (fieldType.FullName != null)
                    {
                        GetKnownTypes(fieldType, serializerTypeInfo);
                    }
                    else
                    {
                        serializerTypeInfo.AddTypeAsHandled(fieldType);
                    }
                }

                // Properties
                var properties = type.GetPropertiesEx(BindingFlagsHelper.GetFinalBindingFlags(false, false, allowNonPublicReflection));
                foreach (var property in properties)
                {
                    var propertyType = property.PropertyType;
                    if (propertyType.FullName != null)
                    {
                        GetKnownTypes(propertyType, serializerTypeInfo);
                    }
                    else
                    {
                        serializerTypeInfo.AddTypeAsHandled(propertyType);
                    }
                }
            }
        }

        /// <summary>
        /// Determines whether the type should be handled.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="serializerTypeInfo">The serializer type info.</param>
        /// <returns><c>true</c> if the type should be handled; otherwise, <c>false</c>.</returns>
        protected virtual bool ShouldTypeBeIgnored(Type type, XmlSerializerTypeInfo serializerTypeInfo)
        {
            if (type == null)
            {
                return true;
            }

            // Note, although resharper says this isn't possible, it might be
            if (type.FullName == null)
            {
                serializerTypeInfo.AddTypeAsHandled(type);
                return true;
            }

            // Ignore non-generic .NET
            if (!type.IsGenericTypeEx() && type.GetSafeFullName().StartsWith("System."))
            {
                // Log.Debug("Non-generic .NET system type, can be ignored");
                serializerTypeInfo.AddTypeAsHandled(type);
                return true;
            }

            return serializerTypeInfo.ContainsKnownType(type) || serializerTypeInfo.IsTypeAlreadyHandled(type);
        }

        /// <summary>
        /// Gets the known types via attributes.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>The list of known types via the <see cref="KnownTypeAttribute"/>.</returns>
        /// <exception cref="ArgumentNullException">The <paramref name="type"/> is <c>null</c>.</exception>
        protected virtual Type[] GetKnownTypesViaAttributes(Type type)
        {
            Argument.IsNotNull("type", type);

            string typeName = type.AssemblyQualifiedName;
            lock (_knownTypesByAttributesCache)
            {
                if (!_knownTypesByAttributesCache.ContainsKey(typeName))
                {
                    var additionalTypes = new List<Type>();
{'fraction_non_alphanumeric': 0.10054017364744477, 'fraction_numerical': 0.0012160977403207105, 'mean_word_length': 1.6360518860891606, 'pattern_counts': {'":': 0, '<': 213, '<?xml version=': 0, '>': 219, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 22}, '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': '3115764', 'n_tokens_mistral': 8187, 'n_tokens_neox': 7780, 'n_words': 2063}