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

namespace Mapper
{
    public static class ObjectMapper
    {
        static readonly MostlyReadDictionary<TypePair, Delegate> MapMethods = new MostlyReadDictionary<TypePair, Delegate>();

        static readonly Subject<string> _trace = new Subject<string>();

        public static IObservable<string> Trace => _trace;

        internal static Func<TIn, TOut> GetOrAddMapping<TIn, TOut>()
        {
            return (Func<TIn, TOut>)MapMethods.GetOrAdd(new TypePair(typeof(TIn), typeof(TOut)), _ => CreateMapDelegate<TIn, TOut>());
        }

        /// <summary>
        /// Fast copying code that generates a method that does the copy from one type to another
        /// </summary>
        static internal Delegate CreateMapDelegate<TIn, TOut>()
        {
            Contract.Requires(typeof(TOut).GetConstructor(Type.EmptyTypes) != null);
            Contract.Ensures(Contract.Result<Delegate>() != null);

            var input = Expression.Parameter(typeof(TIn), "input");
            var result = Expression.Parameter(typeof(TOut), "result");

            var lines = new List<Expression> { Expression.Assign(result, Expression.New(typeof(TOut).GetConstructor(Type.EmptyTypes))) };

            var outByName = Types.WritablePropertiesAndFields<TOut>();
            var inByName = Types.ReadablePropertiesAndFields<TIn>();

            foreach (var inPF in inByName.Select(pair => pair.Value))
            {
                var outPF = FindOutPropertyOrField(outByName, inPF);
                if (outPF == null) continue;

                var outType = Types.PropertyOrFieldType(outPF);
                var inType = Types.PropertyOrFieldType(inPF);
                Expression value = Expression.PropertyOrField(input, inPF.Name);
                if (inType != outType)
                {
                    // type if not the same, can it be assigned?
                    if (Types.CanBeCast(inType, outType))
                    {
                        value = Expression.Convert(value, outType);
                    }
                    else if (Types.IsNullable(inType) && inType.GetGenericArguments()[0] == outType)
                    {
                        value = Expression.Call(value, inType.GetMethod("GetValueOrDefault", Type.EmptyTypes));
                    }
                    else if (Types.IsNullable(inType) && Types.CanBeCast(inType.GetGenericArguments()[0], outType))
                    {
                        value = Expression.Convert(Expression.Call(value, inType.GetMethod("GetValueOrDefault", Type.EmptyTypes)), outType);
                    }
                    else
                    {
                        _trace.OnNext($"Don't know how to map {inPF.DeclaringType}.{inPF.Name} to type {typeof(TOut)}");
                        continue;
                    }
                }
                lines.Add(Expression.Assign(Expression.PropertyOrField(result, outPF.Name), value));
            }
            lines.Add(result); // the return value
            var block = Expression.Block(new[] { result }, lines);
            return Expression.Lambda<Func<TIn, TOut>>(block, input).Compile();
        }

        static MemberInfo FindOutPropertyOrField(IDictionary<string, MemberInfo> outByName, MemberInfo inPF)
        {
            Contract.Requires(outByName != null);
            Contract.Requires(inPF != null);
            foreach (var name in Names.CandidateNames(inPF.Name, Types.PropertyOrFieldType(inPF)))
            {
                MemberInfo outPF;
                if (outByName.TryGetValue(name, out outPF)) // names match
                    return outPF;
            }
            return null;<commit_msg>Better trace messages when trying to map objects<commit_after>﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Mapper
{
    public static class ObjectMapper
    {
        static readonly MostlyReadDictionary<TypePair, Delegate> MapMethods = new MostlyReadDictionary<TypePair, Delegate>();

        static readonly Subject<string> _trace = new Subject<string>();

        public static IObservable<string> Trace => _trace;

        internal static Func<TIn, TOut> GetOrAddMapping<TIn, TOut>()
        {
            return (Func<TIn, TOut>)MapMethods.GetOrAdd(new TypePair(typeof(TIn), typeof(TOut)), _ => CreateMapDelegate<TIn, TOut>());
        }

        /// <summary>
        /// Fast copying code that generates a method that does the copy from one type to another
        /// </summary>
        static internal Delegate CreateMapDelegate<TIn, TOut>()
        {
            Contract.Requires(typeof(TOut).GetConstructor(Type.EmptyTypes) != null);
            Contract.Ensures(Contract.Result<Delegate>() != null);

            var input = Expression.Parameter(typeof(TIn), "input");
            var result = Expression.Parameter(typeof(TOut), "result");

            var lines = new List<Expression> { Expression.Assign(result, Expression.New(typeof(TOut).GetConstructor(Type.EmptyTypes))) };

            var outByName = Types.WritablePropertiesAndFields<TOut>();
            var inByName = Types.ReadablePropertiesAndFields<TIn>();

            foreach (var inPF in inByName.Select(pair => pair.Value))
            {
                var outPF = FindOutPropertyOrField(outByName, inPF);
                if (outPF == null)
                {
                    _trace.OnNext($"Can't find the output property (or field) for {inPF.DeclaringType}.{inPF.Name} on type {typeof(TOut)}");
                    continue;
                }

                var outType = Types.PropertyOrFieldType(outPF);
                var inType = Types.PropertyOrFieldType(inPF);
                Expression value = Expression.PropertyOrField(input, inPF.Name);
                if (inType != outType)
                {
                    // type if not the same, can it be assigned?
                    if (Types.CanBeCast(inType, outType))
                    {
                        value = Expression.Convert(value, outType);
                    }
                    else if (Types.IsNullable(inType) && inType.GetGenericArguments()[0] == outType)
                    {
                        value = Expression.Call(value, inType.GetMethod("GetValueOrDefault", Type.EmptyTypes));
                    }
                    else if (Types.IsNullable(inType) && Types.CanBeCast(inType.GetGenericArguments()[0], outType))
                    {
                        value = Expression.Convert(Expression.Call(value, inType.GetMethod("GetValueOrDefault", Type.EmptyTypes)), outType);
                    }
                    else
                    {
                        _trace.OnNext($"Trying to map {inPF.DeclaringType}.{inPF.Name} to {typeof(TOut)}.{outPF.Name} but don't know how to map {inType} to {outType}");
                        continue;
                    }
                }
                lines.Add(Expression.Assign(Expression.PropertyOrField(result, outPF.Name), value));
            }
            lines.Add(result); // the return value
            var block = Expression.Block(new[] { result }, lines);
            return Expression.Lambda<Func<TIn, TOut>>(block, input).Compile();
        }

        static MemberInfo FindOutPropertyOrField(IDictionary<string, MemberInfo> outByName, MemberInfo inPF)
        {
            Contract.Requires(outByName != null);
            Contract.Requires(inPF != null);
            foreach (var name in Names.CandidateNames(inPF.Name, Types.PropertyOrFieldType(inPF)))
            {
                MemberInfo outPF;
                if (outByName.TryGetValue(name, out outPF)) // names match
                    return outPF;
            }
            return null;
{'fraction_non_alphanumeric': 0.10528292438657987, 'fraction_numerical': 0.000500751126690035, 'mean_word_length': 1.9632789317507418, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 47, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, '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': '5286973', 'n_tokens_mistral': 2079, 'n_tokens_neox': 1999, 'n_words': 452}