<commit_before>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Frog.Orm
{
    public class TypeMapper
    {
        public MappedTypeInfo GetTypeInfo(Type type)
        {
            var typeInfo = new MappedTypeInfo();

            var attribute = ((TableAttribute)Attribute.GetCustomAttribute(type, typeof(TableAttribute)));

            if(attribute == null)
                throw new MappingException(String.Format("Cannot map type '{0}'. It has no [Table] annotation", type.Name));

            var overriddenName = attribute.Name;
            var primaryKey = GetPrimaryKey(type);

            typeInfo.TableName = overriddenName ?? type.Name;
            typeInfo.PrimaryKey = primaryKey != null ? primaryKey.Name : null;

            if(primaryKey != null)
                typeInfo.Columns.Add(primaryKey);

            typeInfo.Columns.AddRange(GetColumns(type));

            return typeInfo;
        }

        private MappedColumnInfo GetPrimaryKey(Type type)
        {
            var primaryKeyProperty = type.GetProperties().FirstOrDefault(c => Attribute.GetCustomAttribute(c, typeof(PrimaryKeyAttribute)) != null);

            if (primaryKeyProperty == null)
                return null;

            var overriddenName = GetAttribute<PrimaryKeyAttribute>(primaryKeyProperty).Name;

            return new MappedColumnInfo
                    {
                        Name = overriddenName ?? primaryKeyProperty.Name,
                        Type = GetColumnType(primaryKeyProperty)
                    };
        }

        private static IEnumerable<MappedColumnInfo> GetColumns(Type type)
        {
            return from property in type.GetProperties()
                   where Attribute.GetCustomAttribute(property, typeof (ColumnAttribute)) != null
                   select new MappedColumnInfo
                              {
                                  Name = GetColumnName(property),
                                  Type = GetColumnType(property)
                              };
        }

        private static Type GetColumnType(PropertyInfo property)
        {
            return property.PropertyType;
        }

        public static string GetColumnName(PropertyInfo property)
        {
            var column = GetAttribute<ColumnAttribute>(property);

            var overriddenName = "";

            if (column != null)
                overriddenName = column.Name;
            else
            {
                var primaryKey = GetAttribute<PrimaryKeyAttribute>(property);
                overriddenName = primaryKey.Name;
            }

            return overriddenName ?? property.Name;
        }

        public static T GetAttribute<T>(PropertyInfo property) where T : Attribute
        {
            return (T)Attribute.GetCustomAttribute(property, typeof(T));
        }

        public Dictionary<string, object> GetInstanceValues(object instance)
        {
            var result = new Dictionary<string, object>();

            var props = from c in instance.GetType().GetProperties()
                        where
                            Attribute.GetCustomAttribute(c, typeof (ColumnAttribute)) != null ||
                            Attribute.GetCustomAttribute(c, typeof (PrimaryKeyAttribute)) != null
                        select new {Name = GetColumnName(c), Value = c.GetValue(instance, null) };


            props.ToList().ForEach(x => result.Add(x.Name, x.Value));

            return result;
        }

        public void SetValueOfPrimaryKey(object instance, object value)
        {
            var props = from c in instance.GetType().GetProperties()
                        where
                            Attribute.GetCustomAttribute(c, typeof (PrimaryKeyAttribute)) != null
                        select c;

            props.ToList().ForEach(c => c.SetValue(instance, MapDbValueToDotNet(c.PropertyType, value), null));
        }

        public static object MapDbValueToDotNet(Type targetType, object value)
        {
            if (targetType.IsEnum)
                return Convert.ToInt32(value);

            if (targetType.Equals(typeof(Double)))
                return Convert.ToDouble(value);

            if (targetType.Equals(typeof(Decimal)))
                return Convert.ToDecimal(value);<commit_msg>Added caching of type info for improved speed.<commit_after>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Frog.Orm
{
    public class TypeMapper
    {
        private static Dictionary<Type, MappedTypeInfo> typeInfoCache;
        private static Dictionary<Type, List<PropertyInfo>> propertyInfoCache;

        public TypeMapper()
        {
            if(typeInfoCache == null)
                typeInfoCache = new Dictionary<Type, MappedTypeInfo>();

            if (propertyInfoCache == null)
                propertyInfoCache = new Dictionary<Type, List<PropertyInfo>>();
        }

        public MappedTypeInfo GetTypeInfo(Type type)
        {
            MappedTypeInfo typeInfo;

            if(!typeInfoCache.TryGetValue(type, out typeInfo))
            {
                typeInfo = new MappedTypeInfo();

                var attribute = ((TableAttribute)Attribute.GetCustomAttribute(type, typeof(TableAttribute)));

                if (attribute == null)
                    throw new MappingException(String.Format("Cannot map type '{0}'. It has no [Table] annotation", type.Name));

                var overriddenName = attribute.Name;
                var primaryKey = GetPrimaryKey(type);

                typeInfo.TableName = overriddenName ?? type.Name;
                typeInfo.PrimaryKey = primaryKey != null ? primaryKey.Name : null;

                if (primaryKey != null)
                    typeInfo.Columns.Add(primaryKey);

                typeInfo.Columns.AddRange(GetColumns(type));

                // Missing thread safety in adding typeinfo to cache.
                typeInfoCache.Add(type, typeInfo);    
            }
            
            return typeInfo;
        }

        private MappedColumnInfo GetPrimaryKey(Type type)
        {
            var primaryKeyProperty = type.GetProperties().FirstOrDefault(c => Attribute.GetCustomAttribute(c, typeof(PrimaryKeyAttribute)) != null);

            if (primaryKeyProperty == null)
                return null;

            var overriddenName = GetAttribute<PrimaryKeyAttribute>(primaryKeyProperty).Name;

            return new MappedColumnInfo
                    {
                        Name = overriddenName ?? primaryKeyProperty.Name,
                        Type = GetColumnType(primaryKeyProperty)
                    };
        }

        private static IEnumerable<MappedColumnInfo> GetColumns(Type type)
        {
            return from property in type.GetProperties()
                   where Attribute.GetCustomAttribute(property, typeof (ColumnAttribute)) != null
                   select new MappedColumnInfo
                              {
                                  Name = GetColumnName(property),
                                  Type = GetColumnType(property)
                              };
        }

        private static Type GetColumnType(PropertyInfo property)
        {
            return property.PropertyType;
        }

        public static string GetColumnName(PropertyInfo property)
        {
            var column = GetAttribute<ColumnAttribute>(property);

            var overriddenName = "";

            if (column != null)
                overriddenName = column.Name;
            else
            {
                var primaryKey = GetAttribute<PrimaryKeyAttribute>(property);
                overriddenName = primaryKey.Name;
            }

            return overriddenName ?? property.Name;
        }

        public static T GetAttribute<T>(PropertyInfo property) where T : Attribute
        {
            return (T)Attribute.GetCustomAttribute(property, typeof(T));
        }

        public Dictionary<string, object> GetInstanceValues(object instance)
        {
            var result = new Dictionary<string, object>();

            var type = instance.GetType();

            List<PropertyInfo> properties;

            if(!propertyInfoCache.TryGetValue(type, out properties))
            {
                properties = new List<PropertyInfo>();

                foreach(var property in type.GetProperties())
                {
                    if(Attribute.GetCustomAttribute(property, typeof(ColumnAttribute)) != null ||
                        Attribute.GetCustomAttribute(property, typeof(PrimaryKeyAttribute)) != null)
                        properties.Add(property);
                }

                propertyInfoCache.Add(type, properties);
            }

            properties.ToList().ForEach(x => result.Add(GetColumnName(x), x.GetValue(instance, null)));
            return result;
        }

        public void SetValueOfPrimaryKey(object instance, object value)
        {
            var props = from c in instance.GetType().GetProperties()
                        where
                            Attribute.GetCustomAttribute(c, typeof (PrimaryKeyAttribute)) != null
                        select c;

            props.ToList().ForEach(c => c.SetValue(instance, MapDbValueToDotNet(c.PropertyType, value), null));
        }

        public static object MapDbValueToDotNet(Type targetType, object value)
        {
            if (targetType.IsEnum)
                return Convert.ToInt32(value);

            if (targetType.Equals(typeof(Double)))
                return Convert.ToDouble(value);

            if (targetType.Equals(typeof(Decimal)))
                return Convert.ToDecimal(value);
{'fraction_non_alphanumeric': 0.08274521836964303, 'fraction_numerical': 0.000613685179502915, 'mean_word_length': 1.759808072255151, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5286948', 'n_tokens_mistral': 2304, 'n_tokens_neox': 2173, 'n_words': 508}