// ========================================================================== // SimpleMapper.cs // PinkParrot Headless CMS // ========================================================================== // Copyright (c) PinkParrot Group // All rights reserved. // ========================================================================== using System; using System.Collections.Generic; using System.Globalization; using System.Linq; // ReSharper disable StaticMemberInGenericType namespace PinkParrot.Infrastructure.Reflection { public static class SimpleMapper { private sealed class ConversionPropertyMapper : PropertyMapper { private readonly Type targetType; public ConversionPropertyMapper(IPropertyAccessor srcAccessor, IPropertyAccessor dstAccessor, Type targetType) : base(srcAccessor, dstAccessor) { this.targetType = targetType; } public override void Map(object source, object destination, CultureInfo culture) { var value = GetValue(source); if (value == null) { return; } object converted; try { converted = Convert.ChangeType(value, targetType, culture); SetValue(destination, converted); } catch (InvalidCastException) { if (targetType == typeof(string)) { converted = value.ToString(); SetValue(destination, converted); } } } } private class PropertyMapper { private readonly IPropertyAccessor srcAccessor; private readonly IPropertyAccessor dstAccessor; public PropertyMapper(IPropertyAccessor srcAccessor, IPropertyAccessor dstAccessor) { this.srcAccessor = srcAccessor; this.dstAccessor = dstAccessor; } public virtual void Map(object source, object destination, CultureInfo culture) { var value = GetValue(source); SetValue(destination, value); } protected void SetValue(object destination, object value) { dstAccessor.Set(destination, value); } protected object GetValue(object source) { return srcAccessor.Get(source); } } private static class ClassMapper where TSource : class where TDestination : class { private static readonly PropertyMapper[] Mappers; private static readonly Type[] Convertibles = { typeof(bool), typeof(byte), typeof(char), typeof(decimal), typeof(float), typeof(double), typeof(short), typeof(int), typeof(long), typeof(string), typeof(DateTime) }; static ClassMapper() { var dstType = typeof(TDestination); var srcType = typeof(TSource); var destinationProperties = dstType.GetPublicProperties(); var newMappers = new List(); foreach (var srcProperty in srcType.GetPublicProperties().Where(x => x.CanRead)) { var dstProperty = destinationProperties.FirstOrDefault(x => x.Name == srcProperty.Name); if (dstProperty == null || !dstProperty.CanWrite) { continue; } var srcPropertyType = srcProperty.PropertyType; var dstPropertyType = dstProperty.PropertyType; if (srcPropertyType == dstPropertyType) { newMappers.Add(new PropertyMapper(new PropertyAccessor(srcType, srcProperty), new PropertyAccessor(dstType, dstProperty))); } else { if (Convertibles.Contains(dstPropertyType)) { newMappers.Add(new ConversionPropertyMapper(new PropertyAccessor(srcType, srcProperty), new PropertyAccessor(dstType, dstProperty), dstPropertyType)); } } } Mappers = newMappers.ToArray(); } public static TDestination Map(TSource source, TDestination destination, CultureInfo culture) { foreach (var mapper in Mappers) { mapper.Map(source, destination, culture); } return destination; } } public static TDestination Map(TSource source, TDestination destination) where TSource : class where TDestination : class { return Map(source, destination, CultureInfo.CurrentCulture); } public static TDestination Map(TSource source, TDestination destination, CultureInfo culture) where TSource : class where TDestination : class { Guard.NotNull(source, nameof(source)); Guard.NotNull(culture, nameof(culture)); Guard.NotNull(destination, nameof(destination)); return ClassMapper.Map(source, destination, culture); } } }