diff --git a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs index 4eb2ed3b5..e42fc7d97 100644 --- a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs @@ -7,7 +7,7 @@ using System.Linq.Expressions; using System.Reflection; using AgileMapper.Configuration; - using DataSources; + using DataSources.Factories; using Extensions; using Extensions.Internal; using Members; @@ -24,7 +24,7 @@ #endif internal class CustomDataSourceTargetMemberSpecifier : - ICustomMappingDataSourceTargetMemberSpecifier, + ICustomDataSourceTargetMemberSpecifier, ICustomProjectionDataSourceTargetMemberSpecifier { private readonly MappingConfigInfo _configInfo; diff --git a/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryMappingTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryMappingTargetMemberSpecifier.cs index d4493a5b0..66c0cfabf 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryMappingTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryMappingTargetMemberSpecifier.cs @@ -4,7 +4,7 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries using System.Linq.Expressions; using AgileMapper.Configuration; using AgileMapper.Configuration.Dictionaries; - using DataSources; + using DataSources.Factories; #if FEATURE_DYNAMIC using Dynamics; #endif diff --git a/AgileMapper/Api/Configuration/ICustomMappingDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/ICustomDataSourceTargetMemberSpecifier.cs similarity index 98% rename from AgileMapper/Api/Configuration/ICustomMappingDataSourceTargetMemberSpecifier.cs rename to AgileMapper/Api/Configuration/ICustomDataSourceTargetMemberSpecifier.cs index b4fe2f6ad..5c7a0b03f 100644 --- a/AgileMapper/Api/Configuration/ICustomMappingDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/ICustomDataSourceTargetMemberSpecifier.cs @@ -8,7 +8,7 @@ /// /// The source type to which the configuration should apply. /// The target type to which the configuration should apply. - public interface ICustomMappingDataSourceTargetMemberSpecifier + public interface ICustomDataSourceTargetMemberSpecifier { /// /// Apply the configuration to the given . diff --git a/AgileMapper/Api/Configuration/IRootMappingConfigurator.cs b/AgileMapper/Api/Configuration/IRootMappingConfigurator.cs index 240926c36..e7ac14641 100644 --- a/AgileMapper/Api/Configuration/IRootMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/IRootMappingConfigurator.cs @@ -127,7 +127,7 @@ ICustomDataSourceMappingConfigContinuation Map - ICustomMappingDataSourceTargetMemberSpecifier Map( + ICustomDataSourceTargetMemberSpecifier Map( Expression, TSourceValue>> valueFactoryExpression); /// @@ -138,10 +138,10 @@ ICustomMappingDataSourceTargetMemberSpecifier MapThe type of the custom value being configured. /// The expression to map to the configured target member. /// - /// An ICustomMappingDataSourceTargetMemberSpecifier with which to specify the target member to which the + /// An ICustomDataSourceTargetMemberSpecifier with which to specify the target member to which the /// custom value should be applied. /// - ICustomMappingDataSourceTargetMemberSpecifier Map( + ICustomDataSourceTargetMemberSpecifier Map( Expression> valueFactoryExpression); /// @@ -152,10 +152,10 @@ ICustomMappingDataSourceTargetMemberSpecifier MapThe type of the custom value being configured. /// The expression to map to the configured target member. /// - /// An ICustomMappingDataSourceTargetMemberSpecifier with which to specify the target member to which the + /// An ICustomDataSourceTargetMemberSpecifier with which to specify the target member to which the /// custom value should be applied. /// - ICustomMappingDataSourceTargetMemberSpecifier Map( + ICustomDataSourceTargetMemberSpecifier Map( Expression> valueFactoryExpression); /// @@ -164,10 +164,10 @@ ICustomMappingDataSourceTargetMemberSpecifier MapThe type of value returned by the given Func. /// The Func object to map to the configured target member. /// - /// An ICustomMappingDataSourceTargetMemberSpecifier with which to specify the target member to which the + /// An ICustomDataSourceTargetMemberSpecifier with which to specify the target member to which the /// custom value should be applied. /// - ICustomMappingDataSourceTargetMemberSpecifier MapFunc( + ICustomDataSourceTargetMemberSpecifier MapFunc( Func valueFunc); /// @@ -177,10 +177,10 @@ ICustomMappingDataSourceTargetMemberSpecifier MapFuncThe type of the custom constant value being configured. /// The constant value to map to the configured target member. /// - /// An ICustomMappingDataSourceTargetMemberSpecifier with which to specify the target member to which the + /// An ICustomDataSourceTargetMemberSpecifier with which to specify the target member to which the /// custom constant value should be applied. /// - ICustomMappingDataSourceTargetMemberSpecifier Map(TSourceValue value); + ICustomDataSourceTargetMemberSpecifier Map(TSourceValue value); /// /// Configure a constant value for the given when mapping from and to the diff --git a/AgileMapper/Api/Configuration/MappingConfigurator.cs b/AgileMapper/Api/Configuration/MappingConfigurator.cs index 757096467..4f1ab57cc 100644 --- a/AgileMapper/Api/Configuration/MappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/MappingConfigurator.cs @@ -349,7 +349,7 @@ public ICustomDataSourceMappingConfigContinuation Map(valueFactoryExpression).To(targetMember); } - public ICustomMappingDataSourceTargetMemberSpecifier Map( + public ICustomDataSourceTargetMemberSpecifier Map( Expression, TSourceValue>> valueFactoryExpression) { return GetValueFactoryTargetMemberSpecifier(valueFactoryExpression); @@ -361,23 +361,23 @@ public ICustomProjectionDataSourceTargetMemberSpecifier Map(valueFactoryExpression); } - public ICustomMappingDataSourceTargetMemberSpecifier Map( + public ICustomDataSourceTargetMemberSpecifier Map( Expression> valueFactoryExpression) { return GetValueFactoryTargetMemberSpecifier(valueFactoryExpression); } - public ICustomMappingDataSourceTargetMemberSpecifier Map( + public ICustomDataSourceTargetMemberSpecifier Map( Expression> valueFactoryExpression) { return GetValueFactoryTargetMemberSpecifier(valueFactoryExpression); } - public ICustomMappingDataSourceTargetMemberSpecifier MapFunc( + public ICustomDataSourceTargetMemberSpecifier MapFunc( Func valueFunc) => GetConstantValueTargetMemberSpecifier(valueFunc); - public ICustomMappingDataSourceTargetMemberSpecifier Map(TSourceValue value) + public ICustomDataSourceTargetMemberSpecifier Map(TSourceValue value) => GetConstantValueTargetMemberSpecifier(value); public IMappingConfigContinuation Map( diff --git a/AgileMapper/Configuration/ConfiguredIgnoredMember.cs b/AgileMapper/Configuration/ConfiguredIgnoredMember.cs index e9fbd8189..b74c46dcb 100644 --- a/AgileMapper/Configuration/ConfiguredIgnoredMember.cs +++ b/AgileMapper/Configuration/ConfiguredIgnoredMember.cs @@ -6,7 +6,7 @@ namespace AgileObjects.AgileMapper.Configuration #else using System.Linq.Expressions; #endif - using DataSources; + using DataSources.Factories; using Members; using ReadableExpressions; diff --git a/AgileMapper/Configuration/Dictionaries/CustomDictionaryKey.cs b/AgileMapper/Configuration/Dictionaries/CustomDictionaryKey.cs index 9be9db8d8..cc11334a5 100644 --- a/AgileMapper/Configuration/Dictionaries/CustomDictionaryKey.cs +++ b/AgileMapper/Configuration/Dictionaries/CustomDictionaryKey.cs @@ -2,7 +2,7 @@ { using System; using System.Dynamic; - using DataSources; + using DataSources.Factories; using Members; using ReadableExpressions.Extensions; #if NET35 diff --git a/AgileMapper/Configuration/UserConfigurationSet.cs b/AgileMapper/Configuration/UserConfigurationSet.cs index d1bb91764..295224e0e 100644 --- a/AgileMapper/Configuration/UserConfigurationSet.cs +++ b/AgileMapper/Configuration/UserConfigurationSet.cs @@ -3,7 +3,13 @@ using System; using System.Collections.Generic; using System.Linq; +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif using DataSources; + using DataSources.Factories; using Dictionaries; using Extensions; using Extensions.Internal; @@ -11,11 +17,6 @@ using ObjectPopulation; using Projection; using ReadableExpressions.Extensions; -#if NET35 - using Microsoft.Scripting.Ast; -#else - using System.Linq.Expressions; -#endif internal class UserConfigurationSet { diff --git a/AgileMapper/DataSources/AdHocDataSource.cs b/AgileMapper/DataSources/AdHocDataSource.cs index 65089290f..a84b3ebab 100644 --- a/AgileMapper/DataSources/AdHocDataSource.cs +++ b/AgileMapper/DataSources/AdHocDataSource.cs @@ -1,6 +1,5 @@ namespace AgileObjects.AgileMapper.DataSources { - using System.Collections.Generic; #if NET35 using Microsoft.Scripting.Ast; #else @@ -27,8 +26,12 @@ public AdHocDataSource( IQualifiedMember sourceMember, Expression value, Expression condition, - ICollection variables = null) - : base(sourceMember, variables ?? Enumerable.EmptyArray, value, condition) + params ParameterExpression[] variables) + : base( + sourceMember, + variables, + value, + condition) { } } diff --git a/AgileMapper/DataSources/ComplexTypeDataSource.cs b/AgileMapper/DataSources/ComplexTypeDataSource.cs new file mode 100644 index 000000000..051e08440 --- /dev/null +++ b/AgileMapper/DataSources/ComplexTypeDataSource.cs @@ -0,0 +1,65 @@ +namespace AgileObjects.AgileMapper.DataSources +{ +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif + using Extensions.Internal; + using Members; + using ObjectPopulation; + using ObjectPopulation.ComplexTypes; + + internal class ComplexTypeDataSource : DataSourceBase + { + private ComplexTypeDataSource(IDataSource wrappedDataSource, Expression mapping) + : base(wrappedDataSource, mapping) + { + } + + private ComplexTypeDataSource(IQualifiedMember sourceMember, Expression mapping) + : base(sourceMember, mapping) + { + } + + #region Factory Methods + + public static IDataSource Create(IObjectMappingData mappingData) + { + var mapping = ComplexTypeMappingExpressionFactory.Instance.Create(mappingData); + + return new ComplexTypeDataSource(mappingData.MapperData.SourceMember, mapping); + } + + public static IDataSource Create( + IDataSource wrappedDataSource, + int dataSourceIndex, + IChildMemberMappingData complexTypeMappingData) + { + var mapping = MappingFactory.GetChildMapping( + wrappedDataSource.SourceMember, + wrappedDataSource.Value, + dataSourceIndex, + complexTypeMappingData); + + return new ComplexTypeDataSource(wrappedDataSource, mapping); + } + + public static IDataSource Create(int dataSourceIndex, IChildMemberMappingData complexTypeMappingData) + { + var complexTypeMapperData = complexTypeMappingData.MapperData; + var relativeMember = complexTypeMapperData.SourceMember.RelativeTo(complexTypeMapperData.SourceMember); + var sourceMemberAccess = relativeMember.GetQualifiedAccess(complexTypeMapperData); + + var mapping = MappingFactory.GetChildMapping( + relativeMember, + sourceMemberAccess, + dataSourceIndex, + complexTypeMappingData); + + return new ComplexTypeDataSource(complexTypeMapperData.SourceMember, mapping); + } + + #endregion + } +} \ No newline at end of file diff --git a/AgileMapper/DataSources/ComplexTypeMappingDataSource.cs b/AgileMapper/DataSources/ComplexTypeMappingDataSource.cs deleted file mode 100644 index 3b9e1f717..000000000 --- a/AgileMapper/DataSources/ComplexTypeMappingDataSource.cs +++ /dev/null @@ -1,55 +0,0 @@ -namespace AgileObjects.AgileMapper.DataSources -{ - using Members; - using ObjectPopulation; -#if NET35 - using Microsoft.Scripting.Ast; -#else - using System.Linq.Expressions; -#endif - - internal class ComplexTypeMappingDataSource : DataSourceBase - { - public ComplexTypeMappingDataSource( - IDataSource complexTypeDataSource, - int dataSourceIndex, - IChildMemberMappingData complexTypeMappingData) - : base( - complexTypeDataSource, - GetMapping(complexTypeDataSource, dataSourceIndex, complexTypeMappingData)) - { - } - - private static Expression GetMapping( - IDataSource complexTypeDataSource, - int dataSourceIndex, - IChildMemberMappingData complexTypeMappingData) - { - var mapping = MappingFactory.GetChildMapping( - complexTypeDataSource.SourceMember, - complexTypeDataSource.Value, - dataSourceIndex, - complexTypeMappingData); - - return mapping; - } - - public ComplexTypeMappingDataSource(int dataSourceIndex, IChildMemberMappingData complexTypeMappingData) - : base(complexTypeMappingData.MapperData.SourceMember, GetMapping(dataSourceIndex, complexTypeMappingData)) - { - } - - private static Expression GetMapping(int dataSourceIndex, IChildMemberMappingData complexTypeMappingData) - { - var complexTypeMapperData = complexTypeMappingData.MapperData; - var relativeMember = complexTypeMapperData.SourceMember.RelativeTo(complexTypeMapperData.SourceMember); - var sourceMemberAccess = relativeMember.GetQualifiedAccess(complexTypeMapperData); - - return MappingFactory.GetChildMapping( - relativeMember, - sourceMemberAccess, - dataSourceIndex, - complexTypeMappingData); - } - } -} \ No newline at end of file diff --git a/AgileMapper/DataSources/DataSourceBase.cs b/AgileMapper/DataSources/DataSourceBase.cs index 06d27f183..785c671cb 100644 --- a/AgileMapper/DataSources/DataSourceBase.cs +++ b/AgileMapper/DataSources/DataSourceBase.cs @@ -29,7 +29,7 @@ protected DataSourceBase(IDataSource wrappedDataSource, Expression value) protected DataSourceBase( IQualifiedMember sourceMember, - ICollection variables, + IList variables, Expression value, Expression condition = null) { @@ -180,26 +180,29 @@ private static bool IsNotOptionalEntityMemberId(IMemberMapperData mapperData) public Expression SourceMemberTypeTest { get; protected set; } public virtual bool IsValid => Value != Constants.EmptyExpression; - - public virtual Expression PreCondition => null; - + public bool IsConditional => Condition != null; public virtual bool IsFallback => false; public virtual Expression Condition { get; } - public ICollection Variables { get; } + public IList Variables { get; } public Expression Value { get; } - public virtual Expression AddPreCondition(Expression population) => population; - - public Expression AddCondition(Expression value, Expression alternateBranch = null) + public virtual Expression Finalise(Expression memberPopulation, Expression alternatePopulation) { - return alternateBranch != null - ? Expression.IfThenElse(Condition, value, alternateBranch) - : Expression.IfThen(Condition, value); + if (IsConditional) + { + memberPopulation = (alternatePopulation != null) + ? Expression.IfThenElse(Condition, memberPopulation, alternatePopulation) + : Expression.IfThen(Condition, memberPopulation); + } + + return memberPopulation; } + + public virtual Expression AddSourceCondition(Expression value) => value; } } \ No newline at end of file diff --git a/AgileMapper/DataSources/DataSourceSet.cs b/AgileMapper/DataSources/DataSourceSet.cs index e52c97457..8fd413f44 100644 --- a/AgileMapper/DataSources/DataSourceSet.cs +++ b/AgileMapper/DataSources/DataSourceSet.cs @@ -1,25 +1,44 @@ namespace AgileObjects.AgileMapper.DataSources { + using System; using System.Collections; using System.Collections.Generic; - using Extensions.Internal; - using Members; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; + using Members; internal class DataSourceSet : IEnumerable { private readonly IList _dataSources; + private readonly Func, IMemberMapperData, Expression> _valueBuilder; private Expression _value; - public DataSourceSet(IMemberMapperData mapperData, params IDataSource[] dataSources) + private DataSourceSet( + IDataSource dataSource, + IMemberMapperData mapperData, + Func, IMemberMapperData, Expression> valueBuilder) { + _dataSources = new[] { dataSource }; MapperData = mapperData; + _valueBuilder = valueBuilder ?? ValueExpressionBuilders.SingleDataSource; + HasValue = dataSource.IsValid; + IsConditional = dataSource.IsConditional; + Variables = dataSource.Variables; + SourceMemberTypeTest = dataSource.SourceMemberTypeTest; + } + + private DataSourceSet( + IList dataSources, + IMemberMapperData mapperData, + Func, IMemberMapperData, Expression> valueBuilder) + { _dataSources = dataSources; - None = dataSources.Length == 0; + MapperData = mapperData; + None = dataSources.Count == 0; if (None) { @@ -27,9 +46,11 @@ public DataSourceSet(IMemberMapperData mapperData, params IDataSource[] dataSour return; } + _valueBuilder = valueBuilder ?? ValueExpressionBuilders.ConditionTree; + var variables = default(List); - for (var i = 0; i < dataSources.Length;) + for (var i = 0; i < dataSources.Count;) { var dataSource = dataSources[i++]; @@ -64,6 +85,28 @@ public DataSourceSet(IMemberMapperData mapperData, params IDataSource[] dataSour : Enumerable.EmptyArray; } + #region Factory Methods + + public static DataSourceSet For( + IDataSource dataSource, + IMemberMapperData mapperData, + Func, IMemberMapperData, Expression> valueBuilder = null) + { + return new DataSourceSet(dataSource, mapperData, valueBuilder); + } + + public static DataSourceSet For( + IList dataSources, + IMemberMapperData mapperData, + Func, IMemberMapperData, Expression> valueBuilder = null) + { + return dataSources.HasOne() + ? For(dataSources.First(), mapperData, valueBuilder) + : new DataSourceSet(dataSources, mapperData, valueBuilder); + } + + #endregion + public IMemberMapperData MapperData { get; } public bool None { get; } @@ -80,33 +123,8 @@ public DataSourceSet(IMemberMapperData mapperData, params IDataSource[] dataSour public int Count => _dataSources.Count; - public Expression ValueExpression => _value ?? (_value = BuildValueExpression()); - - private Expression BuildValueExpression() - { - var value = default(Expression); - - for (var i = _dataSources.Count - 1; i >= 0;) - { - var isFirstDataSource = value == default(Expression); - var dataSource = _dataSources[i--]; - - var dataSourceValue = dataSource.IsConditional - ? Expression.Condition( - dataSource.Condition, - isFirstDataSource - ? dataSource.Value - : dataSource.Value.GetConversionTo(value.Type), - isFirstDataSource - ? dataSource.Value.Type.ToDefaultExpression() - : value) - : dataSource.Value; - - value = dataSource.AddPreConditionIfNecessary(dataSourceValue); - } - - return value; - } + public Expression BuildValue() + => _value ?? (_value = _valueBuilder.Invoke(_dataSources, MapperData)); public Expression GetFinalValueOrNull() { diff --git a/AgileMapper/DataSources/DictionaryEntryDataSource.cs b/AgileMapper/DataSources/DictionaryEntryDataSource.cs index 9783ea227..2eeed8f49 100644 --- a/AgileMapper/DataSources/DictionaryEntryDataSource.cs +++ b/AgileMapper/DataSources/DictionaryEntryDataSource.cs @@ -50,7 +50,12 @@ private static Expression GetValidEntryExistsTest(DictionaryEntryVariablePair di return valueNonNull; } - public override Expression PreCondition => _preCondition ?? (_preCondition = CreatePreCondition()); + public override Expression AddSourceCondition(Expression value) + { + var preCondition = _preCondition ?? (_preCondition = CreatePreCondition()); + + return value.ToIfFalseDefaultCondition(preCondition); + } private Expression CreatePreCondition() { @@ -66,10 +71,12 @@ private Expression CreatePreCondition() return Expression.Block(keyAssignment, matchingKeyExists); } - public override Expression AddPreCondition(Expression population) + public override Expression Finalise(Expression memberPopulation, Expression alternatePopulation) { + memberPopulation = base.Finalise(memberPopulation, alternatePopulation); + var matchingKeyExists = GetMatchingKeyExistsTest(); - var ifKeyExistsPopulate = Expression.IfThen(matchingKeyExists, population); + var ifKeyExistsPopulate = Expression.IfThen(matchingKeyExists, memberPopulation); if (_dictionaryVariables.HasConstantTargetMemberKey) { diff --git a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs index 41ebe079b..a822a42f2 100644 --- a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs +++ b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs @@ -59,7 +59,7 @@ private static string GetTargetMemberName(IBasicMapperData mapperData) public IMemberMapperData MapperData { get; } - public ICollection Variables { get; } + public IList Variables { get; } public ParameterExpression Key => _key ?? (_key = Expression.Variable(SourceMember.KeyType, _targetMemberName + "Key")); diff --git a/AgileMapper/DataSources/EnumerableMappingDataSource.cs b/AgileMapper/DataSources/EnumerableDataSource.cs similarity index 98% rename from AgileMapper/DataSources/EnumerableMappingDataSource.cs rename to AgileMapper/DataSources/EnumerableDataSource.cs index 939a6b48f..95ece2a2b 100644 --- a/AgileMapper/DataSources/EnumerableMappingDataSource.cs +++ b/AgileMapper/DataSources/EnumerableDataSource.cs @@ -13,9 +13,9 @@ using ObjectPopulation; using ObjectPopulation.Enumerables; - internal class EnumerableMappingDataSource : DataSourceBase + internal class EnumerableDataSource : DataSourceBase { - public EnumerableMappingDataSource( + public EnumerableDataSource( IDataSource sourceEnumerableDataSource, int dataSourceIndex, IChildMemberMappingData enumerableMappingData) diff --git a/AgileMapper/DataSources/ConfiguredDataSourceFactory.cs b/AgileMapper/DataSources/Factories/ConfiguredDataSourceFactory.cs similarity index 99% rename from AgileMapper/DataSources/ConfiguredDataSourceFactory.cs rename to AgileMapper/DataSources/Factories/ConfiguredDataSourceFactory.cs index 284cdea9c..e2485dd10 100644 --- a/AgileMapper/DataSources/ConfiguredDataSourceFactory.cs +++ b/AgileMapper/DataSources/Factories/ConfiguredDataSourceFactory.cs @@ -1,15 +1,13 @@ -namespace AgileObjects.AgileMapper.DataSources +namespace AgileObjects.AgileMapper.DataSources.Factories { #if NET35 using System; -#endif - using Configuration; - using Members; -#if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Configuration; + using Members; internal class ConfiguredDataSourceFactory : UserConfiguredItemBase, diff --git a/AgileMapper/DataSources/Finders/ConfiguredDataSourceFinder.cs b/AgileMapper/DataSources/Factories/ConfiguredDataSourcesFactory.cs similarity index 73% rename from AgileMapper/DataSources/Finders/ConfiguredDataSourceFinder.cs rename to AgileMapper/DataSources/Factories/ConfiguredDataSourcesFactory.cs index 7a4f916f6..996364a34 100644 --- a/AgileMapper/DataSources/Finders/ConfiguredDataSourceFinder.cs +++ b/AgileMapper/DataSources/Factories/ConfiguredDataSourcesFactory.cs @@ -1,11 +1,11 @@ -namespace AgileObjects.AgileMapper.DataSources.Finders +namespace AgileObjects.AgileMapper.DataSources.Factories { using System.Collections.Generic; using Extensions.Internal; - internal struct ConfiguredDataSourceFinder : IDataSourceFinder + internal static class ConfiguredDataSourcesFactory { - public IEnumerable FindFor(DataSourceFindContext context) + public static IEnumerable Create(DataSourceFindContext context) { if (context.ConfiguredDataSources.None()) { diff --git a/AgileMapper/DataSources/ConfiguredDictionaryEntryDataSourceFactory.cs b/AgileMapper/DataSources/Factories/ConfiguredDictionaryEntryDataSourceFactory.cs similarity index 95% rename from AgileMapper/DataSources/ConfiguredDictionaryEntryDataSourceFactory.cs rename to AgileMapper/DataSources/Factories/ConfiguredDictionaryEntryDataSourceFactory.cs index 04f72efec..e79ec6f81 100644 --- a/AgileMapper/DataSources/ConfiguredDictionaryEntryDataSourceFactory.cs +++ b/AgileMapper/DataSources/Factories/ConfiguredDictionaryEntryDataSourceFactory.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.DataSources +namespace AgileObjects.AgileMapper.DataSources.Factories { using Configuration; using Members; diff --git a/AgileMapper/DataSources/Finders/DataSourceFindContext.cs b/AgileMapper/DataSources/Factories/DataSourceFindContext.cs similarity index 91% rename from AgileMapper/DataSources/Finders/DataSourceFindContext.cs rename to AgileMapper/DataSources/Factories/DataSourceFindContext.cs index 96f485294..a91b1fe49 100644 --- a/AgileMapper/DataSources/Finders/DataSourceFindContext.cs +++ b/AgileMapper/DataSources/Factories/DataSourceFindContext.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.DataSources.Finders +namespace AgileObjects.AgileMapper.DataSources.Factories { using System.Collections.Generic; using Extensions; @@ -53,12 +53,12 @@ public IDataSource GetFinalDataSource(IDataSource foundDataSource, IChildMemberM if (UseComplexTypeDataSource(foundDataSource, childTargetMember)) { - return new ComplexTypeMappingDataSource(foundDataSource, DataSourceIndex, mappingData); + return ComplexTypeDataSource.Create(foundDataSource, DataSourceIndex, mappingData); } if (childTargetMember.IsEnumerable && foundDataSource.SourceMember.IsEnumerable) { - return new EnumerableMappingDataSource(foundDataSource, DataSourceIndex, mappingData); + return new EnumerableDataSource(foundDataSource, DataSourceIndex, mappingData); } return foundDataSource; diff --git a/AgileMapper/DataSources/Factories/DataSourceSetFactory.cs b/AgileMapper/DataSources/Factories/DataSourceSetFactory.cs new file mode 100644 index 000000000..031e57aa7 --- /dev/null +++ b/AgileMapper/DataSources/Factories/DataSourceSetFactory.cs @@ -0,0 +1,73 @@ +namespace AgileObjects.AgileMapper.DataSources.Factories +{ + using System.Collections.Generic; + using System.Linq; + using Extensions.Internal; + using MappingRoot; + using Members; + using ObjectPopulation; + + internal static class DataSourceSetFactory + { + private static readonly IMappingRootDataSourceFactory[] _mappingRootDataSourceFactories = + { + new QueryProjectionRootDataSourceFactory(), + new EnumMappingRootDataSourceFactory(), + new DictionaryMappingRootDataSourceFactory(), + new EnumerableMappingRootDataSourceFactory(), + new ComplexTypeMappingRootDataSourceFactory() + }; + + private static readonly DataSourcesFactory[] _childDataSourceFactories = + { + ConfiguredDataSourcesFactory.Create, + MaptimeDataSourcesFactory.Create, + SourceMemberDataSourcesFactory.Create, + MetaMemberDataSourcesFactory.Create + }; + + public static DataSourceSet CreateFor(IObjectMappingData rootMappingData) + { + var rootDataSourceFactory = _mappingRootDataSourceFactories + .First(mef => mef.IsFor(rootMappingData)); + + var rootDataSource = rootDataSourceFactory.CreateFor(rootMappingData); + + return DataSourceSet.For(rootDataSource, rootMappingData.MapperData); + } + + public static DataSourceSet CreateFor(IChildMemberMappingData childMappingData) + { + var findContext = new DataSourceFindContext(childMappingData); + var validDataSources = EnumerateDataSources(findContext).ToArray(); + + return DataSourceSet.For(validDataSources, findContext.MapperData); + } + + private static IEnumerable EnumerateDataSources(DataSourceFindContext context) + { + foreach (var factory in _childDataSourceFactories) + { + foreach (var dataSource in factory.Invoke(context)) + { + if (!dataSource.IsValid) + { + continue; + } + + yield return dataSource; + + if (!dataSource.IsConditional) + { + yield break; + } + } + + if (context.StopFind) + { + yield break; + } + } + } + } +} \ No newline at end of file diff --git a/AgileMapper/DataSources/Factories/DataSourcesFactory.cs b/AgileMapper/DataSources/Factories/DataSourcesFactory.cs new file mode 100644 index 000000000..feeb31788 --- /dev/null +++ b/AgileMapper/DataSources/Factories/DataSourcesFactory.cs @@ -0,0 +1,6 @@ +namespace AgileObjects.AgileMapper.DataSources.Factories +{ + using System.Collections.Generic; + + internal delegate IEnumerable DataSourcesFactory(DataSourceFindContext context); +} \ No newline at end of file diff --git a/AgileMapper/DataSources/DefaultValueFallbackDataSourceFactory.cs b/AgileMapper/DataSources/Factories/DefaultValueFallbackDataSourceFactory.cs similarity index 91% rename from AgileMapper/DataSources/DefaultValueFallbackDataSourceFactory.cs rename to AgileMapper/DataSources/Factories/DefaultValueFallbackDataSourceFactory.cs index 84a7d58cc..4f08cae79 100644 --- a/AgileMapper/DataSources/DefaultValueFallbackDataSourceFactory.cs +++ b/AgileMapper/DataSources/Factories/DefaultValueFallbackDataSourceFactory.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.DataSources +namespace AgileObjects.AgileMapper.DataSources.Factories { using Members; diff --git a/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs b/AgileMapper/DataSources/Factories/DerivedComplexTypeDataSourcesFactory.cs similarity index 57% rename from AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs rename to AgileMapper/DataSources/Factories/DerivedComplexTypeDataSourcesFactory.cs index 32635e63c..e4e6d64bb 100644 --- a/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs +++ b/AgileMapper/DataSources/Factories/DerivedComplexTypeDataSourcesFactory.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.ObjectPopulation +namespace AgileObjects.AgileMapper.DataSources.Factories { using System; using System.Collections.Generic; @@ -15,17 +15,19 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using Extensions.Internal; using Members; using NetStandardPolyfills; + using ObjectPopulation; using static Constants; + using static TypeComparer; - internal static class DerivedComplexTypeMappingsFactory + internal static class DerivedComplexTypeDataSourcesFactory { - public static Expression CreateFor(IObjectMappingData declaredTypeMappingData) + public static IList CreateFor(IObjectMappingData declaredTypeMappingData) { var declaredTypeMapperData = declaredTypeMappingData.MapperData; if (DoNotMapDerivedTypes(declaredTypeMapperData)) { - return EmptyExpression; + return Enumerable.EmptyArray; } var derivedSourceTypes = GetDerivedSourceTypesIfNecessary(declaredTypeMapperData); @@ -40,54 +42,41 @@ public static Expression CreateFor(IObjectMappingData declaredTypeMappingData) if (hasNoDerivedSourceTypes && !hasDerivedTargetTypes && !hasDerivedTypePairs) { - return EmptyExpression; + return Enumerable.EmptyArray; } - var derivedTypeMappingExpressions = new List(); + var derivedTypeDataSources = new List(); if (hasDerivedTypePairs) { - AddDeclaredSourceTypeMappings( + AddDeclaredSourceTypeDataSources( derivedTypePairs, declaredTypeMappingData, - derivedTypeMappingExpressions, - out var declaredTypeHasUnconditionalTypePair); + derivedTypeDataSources); - if (declaredTypeHasUnconditionalTypePair && hasNoDerivedSourceTypes) + if (hasNoDerivedSourceTypes && !derivedTypeDataSources.Last().IsConditional) { - return derivedTypeMappingExpressions.First(); + return derivedTypeDataSources; } } - var typedObjectVariables = new List(); - if (hasDerivedSourceTypes) { - AddDerivedSourceTypeMappings( + AddDerivedSourceTypeDataSources( derivedSourceTypes, declaredTypeMappingData, - typedObjectVariables, - derivedTypeMappingExpressions); + derivedTypeDataSources); } if (hasDerivedTargetTypes) { - AddDerivedTargetTypeMappings( + AddDerivedTargetTypeDataSources( declaredTypeMappingData, derivedTargetTypes, - derivedTypeMappingExpressions); - } - - if (derivedTypeMappingExpressions.None()) - { - return EmptyExpression; + derivedTypeDataSources); } - return typedObjectVariables.Any() - ? Block(typedObjectVariables, derivedTypeMappingExpressions) - : derivedTypeMappingExpressions.HasOne() - ? derivedTypeMappingExpressions.First() - : Block(derivedTypeMappingExpressions); + return derivedTypeDataSources; } private static bool DoNotMapDerivedTypes(IMemberMapperData mapperData) @@ -114,16 +103,24 @@ private static ICollection GetDerivedTargetTypesIfNecessary(IMemberMapperD : EmptyTypeArray; } - private static void AddDeclaredSourceTypeMappings( + private static ICollection GetTypePairsFor(IBasicMapperData pairTestMapperData, IMemberMapperData mapperData) + { + var derivedTypePairs = mapperData.MapperContext.UserConfigurations + .DerivedTypes + .GetDerivedTypePairsFor(pairTestMapperData, mapperData.MapperContext); + + return derivedTypePairs; + } + + private static void AddDeclaredSourceTypeDataSources( IEnumerable derivedTypePairs, IObjectMappingData declaredTypeMappingData, - ICollection derivedTypeMappingExpressions, - out bool declaredTypeHasUnconditionalTypePair) + ICollection derivedTypeDataSources) { var declaredTypeMapperData = declaredTypeMappingData.MapperData; derivedTypePairs = derivedTypePairs - .OrderBy(tp => tp.DerivedSourceType, TypeComparer.MostToLeastDerived); + .OrderBy(tp => tp.DerivedSourceType, MostToLeastDerived); foreach (var derivedTypePair in derivedTypePairs) { @@ -137,36 +134,33 @@ private static void AddDeclaredSourceTypeMappings( var derivedTypeMapping = DerivedMappingFactory.GetDerivedTypeMapping( declaredTypeMappingData, sourceValue, - derivedTypePair.DerivedTargetType); + derivedTypePair.DerivedTargetType, + out var derivedTypeMappingData); if (sourceValueCondition != null) { - derivedTypeMapping = Condition( - sourceValueCondition, - derivedTypeMapping, - derivedTypeMapping.Type.ToDefaultExpression()); + derivedTypeMapping = derivedTypeMapping.ToIfFalseDefaultCondition(sourceValueCondition); } - var returnMappingResult = Return(declaredTypeMapperData.ReturnLabelTarget, derivedTypeMapping); + var returnMappingResult = GetReturnMappingResultExpression(declaredTypeMapperData, derivedTypeMapping); - if (condition == null) + var derivedTypeMappingDataSource = new DerivedComplexTypeDataSource( + derivedTypeMappingData.MapperData.SourceMember, + condition, + returnMappingResult); + + derivedTypeDataSources.Add(derivedTypeMappingDataSource); + + if (!derivedTypeMappingDataSource.IsConditional) { - declaredTypeHasUnconditionalTypePair = true; - derivedTypeMappingExpressions.Add(returnMappingResult); return; } - - var ifConditionThenMap = IfThen(condition, returnMappingResult); - - derivedTypeMappingExpressions.Add(ifConditionThenMap); } - - declaredTypeHasUnconditionalTypePair = false; } private static Expression GetTypePairCondition(DerivedTypePair derivedTypePair, IMemberMapperData declaredTypeMapperData) { - var condition = GetTargetValidCheckOrNull(derivedTypePair.DerivedTargetType, declaredTypeMapperData); + var condition = declaredTypeMapperData.GetTargetValidCheckOrNull(derivedTypePair.DerivedTargetType); if (!derivedTypePair.HasConfiguredCondition) { @@ -217,26 +211,20 @@ private static Expression GetDerivedTypeSourceValue( ExpressionEvaluation.Equivalator); } - private static void AddDerivedSourceTypeMappings( + private static void AddDerivedSourceTypeDataSources( IEnumerable derivedSourceTypes, IObjectMappingData declaredTypeMappingData, - ICollection typedObjectVariables, - IList derivedTypeMappingExpressions) + IList derivedTypeDataSources) { var declaredTypeMapperData = declaredTypeMappingData.MapperData; - var insertionOffset = derivedTypeMappingExpressions.Count; + var insertionOffset = derivedTypeDataSources.Count; var orderedDerivedSourceTypes = derivedSourceTypes - .OrderBy(t => t, TypeComparer.MostToLeastDerived); + .OrderBy(t => t, MostToLeastDerived); foreach (var derivedSourceType in orderedDerivedSourceTypes) { var derivedSourceCheck = new DerivedSourceTypeCheck(derivedSourceType); - var typedVariableAssignment = derivedSourceCheck.GetTypedVariableAssignment(declaredTypeMapperData); - - typedObjectVariables.Add(derivedSourceCheck.TypedVariable); - derivedTypeMappingExpressions.Insert(typedVariableAssignment, insertionOffset); - var targetType = declaredTypeMapperData.TargetType.GetRuntimeTargetType(derivedSourceType); var outerCondition = derivedSourceCheck.TypeCheck; @@ -244,17 +232,17 @@ private static void AddDerivedSourceTypeMappings( var derivedTypePairs = GetTypePairsFor(derivedSourceType, targetType, declaredTypeMapperData); - Expression ifSourceVariableIsDerivedTypeThenMap; + IDataSource sourceVariableIsDerivedTypeDataSource; if (derivedTypePairs.None()) { - ifSourceVariableIsDerivedTypeThenMap = GetIfConditionThenMapExpression( + sourceVariableIsDerivedTypeDataSource = GetReturnMappingResultDataSource( declaredTypeMappingData, outerCondition, - derivedSourceCheck.TypedVariable, + derivedSourceCheck, targetType); - derivedTypeMappingExpressions.Insert(ifSourceVariableIsDerivedTypeThenMap, insertionOffset); + derivedTypeDataSources.Insert(sourceVariableIsDerivedTypeDataSource, insertionOffset); continue; } @@ -266,150 +254,151 @@ private static void AddDerivedSourceTypeMappings( if (hasUnconditionalDerivedTargetTypeMapping) { - ifSourceVariableIsDerivedTypeThenMap = GetIfConditionThenMapExpression( + sourceVariableIsDerivedTypeDataSource = GetReturnMappingResultDataSource( declaredTypeMappingData, outerCondition, - derivedSourceCheck.TypedVariable, + derivedSourceCheck, unconditionalDerivedTargetType); - derivedTypeMappingExpressions.Insert(ifSourceVariableIsDerivedTypeThenMap, insertionOffset); + derivedTypeDataSources.Insert(sourceVariableIsDerivedTypeDataSource, insertionOffset); continue; } - ifSourceVariableIsDerivedTypeThenMap = GetMapFromConditionOrDefaultExpression( + sourceVariableIsDerivedTypeDataSource = GetMapFromConditionOrDefaultDataSource( declaredTypeMappingData, outerCondition, - derivedSourceCheck.TypedVariable, + derivedSourceCheck, groupedTypePairs, targetType); - derivedTypeMappingExpressions.Insert(ifSourceVariableIsDerivedTypeThenMap, insertionOffset); + derivedTypeDataSources.Insert(sourceVariableIsDerivedTypeDataSource, insertionOffset); } } - private static bool HasUnconditionalDerivedTargetTypeMapping( - IEnumerable derivedTypePairs, - IMemberMapperData declaredTypeMapperData, - out Type unconditionalDerivedTargetType, - out TypePairGroup[] groupedTypePairs) + private static IDataSource GetMapFromConditionOrDefaultDataSource( + IObjectMappingData declaredTypeMappingData, + Expression condition, + DerivedSourceTypeCheck derivedSourceCheck, + IEnumerable typePairGroups, + Type targetType) { - groupedTypePairs = derivedTypePairs - .GroupBy(tp => tp.DerivedTargetType) - .Project(group => new TypePairGroup(group)) - .OrderBy(tp => tp.DerivedTargetType, TypeComparer.MostToLeastDerived) - .ToArray(); + var declaredTypeMapperData = declaredTypeMappingData.MapperData; + var typePairDataSources = new List(); - var unconditionalTypePairs = groupedTypePairs - .Filter(tpg => tpg.TypePairs.None(tp => tp.HasConfiguredCondition)); + Expression derivedTypeMapping; + IObjectMappingData derivedTypeMappingData; - foreach (var unconditionalTypePair in unconditionalTypePairs) + foreach (var typePairGroup in typePairGroups) { - var typePairsCondition = GetTargetValidCheckOrNull( - unconditionalTypePair.DerivedTargetType, - declaredTypeMapperData); + var typePairsCondition = + declaredTypeMapperData.GetTypePairsCondition(typePairGroup.TypePairs) ?? + declaredTypeMapperData.GetTargetValidCheckOrNull(typePairGroup.DerivedTargetType); - if (typePairsCondition == null) - { - unconditionalDerivedTargetType = unconditionalTypePair.DerivedTargetType; - return true; - } + derivedTypeMapping = GetReturnMappingResultExpression( + declaredTypeMappingData, + derivedSourceCheck.TypedVariable, + typePairGroup.DerivedTargetType, + out derivedTypeMappingData); + + var typePairDataSource = new DerivedComplexTypeDataSource( + derivedTypeMappingData.MapperData.SourceMember, + typePairsCondition, + derivedTypeMapping); + + typePairDataSources.Add(typePairDataSource); } - unconditionalDerivedTargetType = null; - return false; + var derivedTargetTypeDataSources = DataSourceSet.For( + typePairDataSources, + declaredTypeMapperData, + ValueExpressionBuilders.ValueSequence); + + derivedTypeMapping = GetReturnMappingResultExpression( + declaredTypeMappingData, + derivedSourceCheck.TypedVariable, + targetType, + out derivedTypeMappingData); + + var derivedTypeMappings = Block( + derivedTargetTypeDataSources.BuildValue(), + derivedTypeMapping); + + return new DerivedComplexTypeDataSource( + derivedTypeMappingData.MapperData.SourceMember, + derivedSourceCheck, + condition, + derivedTypeMappings, + declaredTypeMapperData); } - private static void AddDerivedTargetTypeMappings( - IObjectMappingData declaredTypeMappingData, - IEnumerable derivedTargetTypes, - ICollection derivedTypeMappingExpressions) + private static Expression GetTypePairsCondition( + this IMemberMapperData mapperData, + IEnumerable derivedTypePairs) { - var declaredTypeMapperData = declaredTypeMappingData.MapperData; - - derivedTargetTypes = derivedTargetTypes - .OrderBy(t => t, TypeComparer.MostToLeastDerived); - - foreach (var derivedTargetType in derivedTargetTypes) - { - var targetTypeCondition = GetTargetIsDerivedTypeCheck(derivedTargetType, declaredTypeMapperData); + var conditionalPairs = derivedTypePairs + .Filter(pair => pair.HasConfiguredCondition) + .ToArray(); - var ifDerivedTargetTypeThenMap = GetIfConditionThenMapExpression( - declaredTypeMappingData, - targetTypeCondition, - declaredTypeMapperData.SourceObject, - derivedTargetType); + var pairConditions = conditionalPairs.Chain( + firstPair => firstPair.GetConditionOrNull(mapperData), + (conditionSoFar, pair) => OrElse( + conditionSoFar, + pair.GetConditionOrNull(mapperData))); - derivedTypeMappingExpressions.AddUnlessNullOrEmpty(ifDerivedTargetTypeThenMap); - } + return pairConditions; } - private static Expression GetIfConditionThenMapExpression( - IObjectMappingData mappingData, + private static Expression AppendTargetValidCheckIfAppropriate( Expression condition, - Expression sourceValue, - Type targetType) + Type targetType, + IMemberMapperData mapperData) { - var returnMappingResult = GetReturnMappingResultExpression(mappingData, sourceValue, targetType); - - if (returnMappingResult == EmptyExpression) + if (targetType == mapperData.TargetType) { - return EmptyExpression; + return condition; } - var ifConditionThenMap = IfThen(condition, returnMappingResult); - - return ifConditionThenMap; - } - - private static Expression GetReturnMappingResultExpression( - IObjectMappingData mappingData, - Expression sourceValue, - Type targetType) - { - var mapping = DerivedMappingFactory.GetDerivedTypeMapping(mappingData, sourceValue, targetType); + var targetIsValid = mapperData.GetTargetValidCheckOrNull(targetType); - if (mapping == EmptyExpression) + if (targetIsValid == null) { - return mapping; + return condition; } - var returnMappingResult = Return(mappingData.MapperData.ReturnLabelTarget, mapping); + condition = AndAlso(condition, targetIsValid); - return returnMappingResult; + return condition; } - private static Expression GetMapFromConditionOrDefaultExpression( - IObjectMappingData mappingData, - Expression condition, - Expression typedVariable, - IEnumerable typePairGroups, - Type targetType) + private static bool HasUnconditionalDerivedTargetTypeMapping( + IEnumerable derivedTypePairs, + IMemberMapperData declaredTypeMapperData, + out Type unconditionalDerivedTargetType, + out TypePairGroup[] groupedTypePairs) { - var mappingExpressions = new List(); + groupedTypePairs = derivedTypePairs + .GroupBy(tp => tp.DerivedTargetType) + .Project(group => new TypePairGroup(group)) + .OrderBy(tp => tp.DerivedTargetType, MostToLeastDerived) + .ToArray(); - foreach (var typePairGroup in typePairGroups) - { - var typePairsCondition = - GetTypePairsCondition(typePairGroup.TypePairs, mappingData.MapperData) ?? - GetTargetValidCheckOrNull(typePairGroup.DerivedTargetType, mappingData.MapperData); + var unconditionalTypePairs = groupedTypePairs + .Filter(tpg => tpg.TypePairs.None(tp => tp.HasConfiguredCondition)); - var ifTypePairsConditionThenMap = GetIfConditionThenMapExpression( - mappingData, - typePairsCondition, - typedVariable, - typePairGroup.DerivedTargetType); + foreach (var unconditionalTypePair in unconditionalTypePairs) + { + var typePairsCondition = declaredTypeMapperData + .GetTargetValidCheckOrNull(unconditionalTypePair.DerivedTargetType); - mappingExpressions.Add(ifTypePairsConditionThenMap); + if (typePairsCondition == null) + { + unconditionalDerivedTargetType = unconditionalTypePair.DerivedTargetType; + return true; + } } - var mapToDeclaredTargetType = - GetReturnMappingResultExpression(mappingData, typedVariable, targetType); - - mappingExpressions.Add(mapToDeclaredTargetType); - - var ifSourceVariableIsDerivedTypeThenMap = IfThen(condition, Block(mappingExpressions)); - - return ifSourceVariableIsDerivedTypeThenMap; + unconditionalDerivedTargetType = null; + return false; } private static ICollection GetTypePairsFor( @@ -427,62 +416,87 @@ private static ICollection GetTypePairsFor( return GetTypePairsFor(pairTestMapperData, mapperData); } - private static ICollection GetTypePairsFor(IBasicMapperData pairTestMapperData, IMemberMapperData mapperData) + private static void AddDerivedTargetTypeDataSources( + IObjectMappingData declaredTypeMappingData, + IEnumerable derivedTargetTypes, + ICollection derivedTypeDataSources) { - var derivedTypePairs = mapperData.MapperContext.UserConfigurations - .DerivedTypes - .GetDerivedTypePairsFor(pairTestMapperData, mapperData.MapperContext); + var declaredTypeMapperData = declaredTypeMappingData.MapperData; - return derivedTypePairs; - } + derivedTargetTypes = derivedTargetTypes.OrderBy(t => t, MostToLeastDerived); - private static Expression GetTypePairsCondition( - IEnumerable derivedTypePairs, - IMemberMapperData mapperData) - { - var conditionalPairs = derivedTypePairs - .Filter(pair => pair.HasConfiguredCondition) - .ToArray(); + foreach (var derivedTargetType in derivedTargetTypes) + { + var targetTypeCondition = declaredTypeMapperData.GetTargetIsDerivedTypeCheck(derivedTargetType); - var pairConditions = conditionalPairs.Chain( - firstPair => firstPair.GetConditionOrNull(mapperData), - (conditionSoFar, pair) => OrElse( - conditionSoFar, - pair.GetConditionOrNull(mapperData))); + var derivedTypeMapping = GetReturnMappingResultExpression( + declaredTypeMappingData, + declaredTypeMapperData.SourceObject, + derivedTargetType, + out var derivedTypeMappingData); - return pairConditions; + if (derivedTypeMapping == EmptyExpression) + { + continue; + } + + var derivedTargetTypeDataSouce = new DerivedComplexTypeDataSource( + derivedTypeMappingData.MapperData.SourceMember, + targetTypeCondition, + derivedTypeMapping); + + derivedTypeDataSources.Add(derivedTargetTypeDataSouce); + } } - private static Expression AppendTargetValidCheckIfAppropriate( + private static IDataSource GetReturnMappingResultDataSource( + IObjectMappingData declaredTypeMappingData, Expression condition, - Type targetType, - IMemberMapperData mapperData) + DerivedSourceTypeCheck derivedSourceCheck, + Type targetType) { - if (targetType == mapperData.TargetType) - { - return condition; - } - - var targetIsValid = GetTargetValidCheckOrNull(targetType, mapperData); - - if (targetIsValid == null) - { - return condition; - } + var derivedTypeMapping = GetReturnMappingResultExpression( + declaredTypeMappingData, + derivedSourceCheck.TypedVariable, + targetType, + out var derivedTypeMappingData); + + return new DerivedComplexTypeDataSource( + derivedTypeMappingData.MapperData.SourceMember, + derivedSourceCheck, + condition, + derivedTypeMapping, + declaredTypeMappingData.MapperData); + } - condition = AndAlso(condition, targetIsValid); + private static Expression GetReturnMappingResultExpression( + IObjectMappingData declaredTypeMappingData, + Expression sourceValue, + Type targetType, + out IObjectMappingData derivedTypeMappingData) + { + var mapping = DerivedMappingFactory.GetDerivedTypeMapping( + declaredTypeMappingData, + sourceValue, + targetType, + out derivedTypeMappingData); - return condition; + return (mapping != EmptyExpression) + ? GetReturnMappingResultExpression(declaredTypeMappingData.MapperData, mapping) + : mapping; } - private static Expression GetTargetValidCheckOrNull(Type targetType, IMemberMapperData mapperData) + private static Expression GetReturnMappingResultExpression(ObjectMapperData mapperData, Expression mapping) + => Return(mapperData.ReturnLabelTarget, mapping, mapperData.TargetType); + + private static Expression GetTargetValidCheckOrNull(this IMemberMapperData mapperData, Type targetType) { if (!mapperData.TargetMember.IsReadable || mapperData.TargetIsDefinitelyUnpopulated()) { return null; } - var targetIsOfDerivedType = GetTargetIsDerivedTypeCheck(targetType, mapperData); + var targetIsOfDerivedType = mapperData.GetTargetIsDerivedTypeCheck(targetType); if (mapperData.TargetIsDefinitelyPopulated()) { @@ -495,26 +509,52 @@ private static Expression GetTargetValidCheckOrNull(Type targetType, IMemberMapp return targetIsValid; } - private static Expression GetTargetIsDerivedTypeCheck(Type targetType, IMemberMapperData mapperData) + private static Expression GetTargetIsDerivedTypeCheck(this IMemberMapperData mapperData, Type targetType) => TypeIs(mapperData.TargetObject, targetType); - private static void Insert(this IList mappingExpressions, Expression mapping, int insertionOffset) + private class DerivedComplexTypeDataSource : DataSourceBase { - var insertionIndex = mappingExpressions.Count - insertionOffset; - mappingExpressions.Insert(insertionIndex, mapping); - } + private readonly Expression _typedVariableAssignment; - private class TypePairGroup - { - public TypePairGroup(IGrouping typePairGroup) + public DerivedComplexTypeDataSource( + IQualifiedMember sourceMember, + Expression condition, + Expression value) + : base(sourceMember, Enumerable.EmptyArray, value, condition) + { + } + + public DerivedComplexTypeDataSource( + IQualifiedMember sourceMember, + DerivedSourceTypeCheck derivedSourceCheck, + Expression condition, + Expression value, + IMemberMapperData declaredTypeMapperData) + : base(sourceMember, new[] { derivedSourceCheck.TypedVariable }, value, condition) { - DerivedTargetType = typePairGroup.Key; - TypePairs = typePairGroup.ToArray(); + _typedVariableAssignment = derivedSourceCheck + .GetTypedVariableAssignment(declaredTypeMapperData); } - public Type DerivedTargetType { get; } + public override Expression AddSourceCondition(Expression value) + { + return (_typedVariableAssignment != null) + ? Block(_typedVariableAssignment, value) + : base.AddSourceCondition(value); + } + } + } - public IList TypePairs { get; } + internal class TypePairGroup + { + public TypePairGroup(IGrouping typePairGroup) + { + DerivedTargetType = typePairGroup.Key; + TypePairs = typePairGroup.ToArray(); } + + public Type DerivedTargetType { get; } + + public IList TypePairs { get; } } } \ No newline at end of file diff --git a/AgileMapper/DataSources/DictionaryDataSourceFactory.cs b/AgileMapper/DataSources/Factories/DictionaryDataSourceFactory.cs similarity index 97% rename from AgileMapper/DataSources/DictionaryDataSourceFactory.cs rename to AgileMapper/DataSources/Factories/DictionaryDataSourceFactory.cs index 3d3a99d8e..401e819f5 100644 --- a/AgileMapper/DataSources/DictionaryDataSourceFactory.cs +++ b/AgileMapper/DataSources/Factories/DictionaryDataSourceFactory.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.DataSources +namespace AgileObjects.AgileMapper.DataSources.Factories { using System; using System.Collections.Generic; diff --git a/AgileMapper/DataSources/ExistingOrDefaultValueFallbackDataSourceFactory.cs b/AgileMapper/DataSources/Factories/ExistingOrDefaultValueFallbackDataSourceFactory.cs similarity index 97% rename from AgileMapper/DataSources/ExistingOrDefaultValueFallbackDataSourceFactory.cs rename to AgileMapper/DataSources/Factories/ExistingOrDefaultValueFallbackDataSourceFactory.cs index f136fd257..38f5ed30c 100644 --- a/AgileMapper/DataSources/ExistingOrDefaultValueFallbackDataSourceFactory.cs +++ b/AgileMapper/DataSources/Factories/ExistingOrDefaultValueFallbackDataSourceFactory.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.DataSources +namespace AgileObjects.AgileMapper.DataSources.Factories { #if NET35 using Microsoft.Scripting.Ast; diff --git a/AgileMapper/DataSources/IFallbackDataSourceFactory.cs b/AgileMapper/DataSources/Factories/IFallbackDataSourceFactory.cs similarity index 71% rename from AgileMapper/DataSources/IFallbackDataSourceFactory.cs rename to AgileMapper/DataSources/Factories/IFallbackDataSourceFactory.cs index d3d4c2ddf..0cd813e6b 100644 --- a/AgileMapper/DataSources/IFallbackDataSourceFactory.cs +++ b/AgileMapper/DataSources/Factories/IFallbackDataSourceFactory.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.DataSources +namespace AgileObjects.AgileMapper.DataSources.Factories { using Members; diff --git a/AgileMapper/DataSources/IMaptimeDataSourceFactory.cs b/AgileMapper/DataSources/Factories/IMaptimeDataSourceFactory.cs similarity index 81% rename from AgileMapper/DataSources/IMaptimeDataSourceFactory.cs rename to AgileMapper/DataSources/Factories/IMaptimeDataSourceFactory.cs index 0834e3fa8..25920ac35 100644 --- a/AgileMapper/DataSources/IMaptimeDataSourceFactory.cs +++ b/AgileMapper/DataSources/Factories/IMaptimeDataSourceFactory.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.DataSources +namespace AgileObjects.AgileMapper.DataSources.Factories { using System.Collections.Generic; using Members; diff --git a/AgileMapper/DataSources/Factories/MappingRoot/ComplexTypeMappingRootDataSourceFactory.cs b/AgileMapper/DataSources/Factories/MappingRoot/ComplexTypeMappingRootDataSourceFactory.cs new file mode 100644 index 000000000..83536d01b --- /dev/null +++ b/AgileMapper/DataSources/Factories/MappingRoot/ComplexTypeMappingRootDataSourceFactory.cs @@ -0,0 +1,12 @@ +namespace AgileObjects.AgileMapper.DataSources.Factories.MappingRoot +{ + using ObjectPopulation; + + internal class ComplexTypeMappingRootDataSourceFactory : IMappingRootDataSourceFactory + { + public bool IsFor(IObjectMappingData mappingData) => true; + + public IDataSource CreateFor(IObjectMappingData mappingData) + => ComplexTypeDataSource.Create(mappingData); + } +} \ No newline at end of file diff --git a/AgileMapper/DataSources/Factories/MappingRoot/DictionaryMappingRootDataSourceFactory.cs b/AgileMapper/DataSources/Factories/MappingRoot/DictionaryMappingRootDataSourceFactory.cs new file mode 100644 index 000000000..72bff05a0 --- /dev/null +++ b/AgileMapper/DataSources/Factories/MappingRoot/DictionaryMappingRootDataSourceFactory.cs @@ -0,0 +1,38 @@ +namespace AgileObjects.AgileMapper.DataSources.Factories.MappingRoot +{ + using Members.Dictionaries; + using ObjectPopulation; + + internal class DictionaryMappingRootDataSourceFactory : MappingRootDataSourceFactoryBase + { + public DictionaryMappingRootDataSourceFactory() + : base(new DictionaryMappingExpressionFactory()) + { + } + + public override bool IsFor(IObjectMappingData mappingData) + { + if (mappingData.MapperData.TargetMember.IsDictionary) + { + return true; + } + + if (mappingData.IsRoot) + { + return false; + } + + if (!(mappingData.MapperData.TargetMember is DictionaryTargetMember dictionaryMember)) + { + return false; + } + + if (dictionaryMember.HasSimpleEntries) + { + return true; + } + + return dictionaryMember.HasObjectEntries && !mappingData.IsStandalone(); + } + } +} \ No newline at end of file diff --git a/AgileMapper/DataSources/Factories/MappingRoot/EnumMappingRootDataSourceFactory.cs b/AgileMapper/DataSources/Factories/MappingRoot/EnumMappingRootDataSourceFactory.cs new file mode 100644 index 000000000..a48f25231 --- /dev/null +++ b/AgileMapper/DataSources/Factories/MappingRoot/EnumMappingRootDataSourceFactory.cs @@ -0,0 +1,17 @@ +namespace AgileObjects.AgileMapper.DataSources.Factories.MappingRoot +{ + using NetStandardPolyfills; + using ObjectPopulation; + using ReadableExpressions.Extensions; + + internal class EnumMappingRootDataSourceFactory : MappingRootDataSourceFactoryBase + { + public EnumMappingRootDataSourceFactory() + : base(new EnumMappingExpressionFactory()) + { + } + + public override bool IsFor(IObjectMappingData mappingData) + => mappingData.MapperData.TargetType.GetNonNullableType().IsEnum(); + } +} diff --git a/AgileMapper/DataSources/Factories/MappingRoot/EnumerableMappingRootDataSourceFactory.cs b/AgileMapper/DataSources/Factories/MappingRoot/EnumerableMappingRootDataSourceFactory.cs new file mode 100644 index 000000000..d775bcbbb --- /dev/null +++ b/AgileMapper/DataSources/Factories/MappingRoot/EnumerableMappingRootDataSourceFactory.cs @@ -0,0 +1,16 @@ +namespace AgileObjects.AgileMapper.DataSources.Factories.MappingRoot +{ + using ObjectPopulation; + using ObjectPopulation.Enumerables; + + internal class EnumerableMappingRootDataSourceFactory : MappingRootDataSourceFactoryBase + { + public EnumerableMappingRootDataSourceFactory() + : base(new EnumerableMappingExpressionFactory()) + { + } + + public override bool IsFor(IObjectMappingData mappingData) + => mappingData.MapperData.TargetMember.IsEnumerable; + } +} \ No newline at end of file diff --git a/AgileMapper/DataSources/Factories/MappingRoot/IMappingRootDataSourceFactory.cs b/AgileMapper/DataSources/Factories/MappingRoot/IMappingRootDataSourceFactory.cs new file mode 100644 index 000000000..7da3cf766 --- /dev/null +++ b/AgileMapper/DataSources/Factories/MappingRoot/IMappingRootDataSourceFactory.cs @@ -0,0 +1,11 @@ +namespace AgileObjects.AgileMapper.DataSources.Factories.MappingRoot +{ + using ObjectPopulation; + + internal interface IMappingRootDataSourceFactory + { + bool IsFor(IObjectMappingData mappingData); + + IDataSource CreateFor(IObjectMappingData mappingData); + } +} \ No newline at end of file diff --git a/AgileMapper/DataSources/Factories/MappingRoot/MappingRootDataSourceFactoryBase.cs b/AgileMapper/DataSources/Factories/MappingRoot/MappingRootDataSourceFactoryBase.cs new file mode 100644 index 000000000..d4ab60814 --- /dev/null +++ b/AgileMapper/DataSources/Factories/MappingRoot/MappingRootDataSourceFactoryBase.cs @@ -0,0 +1,25 @@ +namespace AgileObjects.AgileMapper.DataSources.Factories.MappingRoot +{ + using ObjectPopulation; + + internal abstract class MappingRootDataSourceFactoryBase : IMappingRootDataSourceFactory + { + private readonly MappingExpressionFactoryBase _mappingExpressionFactory; + + protected MappingRootDataSourceFactoryBase(MappingExpressionFactoryBase mappingExpressionFactory) + { + _mappingExpressionFactory = mappingExpressionFactory; + } + + public abstract bool IsFor(IObjectMappingData mappingData); + + public IDataSource CreateFor(IObjectMappingData mappingData) + { + var mappingExpression = _mappingExpressionFactory.Create(mappingData); + + return new AdHocDataSource( + mappingData.MapperData.SourceMember, + mappingExpression); + } + } +} \ No newline at end of file diff --git a/AgileMapper/DataSources/Factories/MappingRoot/QueryProjectionRootDataSourceFactory.cs b/AgileMapper/DataSources/Factories/MappingRoot/QueryProjectionRootDataSourceFactory.cs new file mode 100644 index 000000000..ee41f5e55 --- /dev/null +++ b/AgileMapper/DataSources/Factories/MappingRoot/QueryProjectionRootDataSourceFactory.cs @@ -0,0 +1,24 @@ +namespace AgileObjects.AgileMapper.DataSources.Factories.MappingRoot +{ + using Extensions.Internal; + using ObjectPopulation; + using Queryables; + + internal class QueryProjectionRootDataSourceFactory : MappingRootDataSourceFactoryBase + { + public QueryProjectionRootDataSourceFactory() + : base(new QueryProjectionExpressionFactory()) + { + } + + public override bool IsFor(IObjectMappingData mappingData) + { + var mapperData = mappingData.MapperData; + + return mapperData.IsRoot && + mapperData.TargetMember.IsEnumerable && + (mappingData.MappingContext.RuleSet.Name == Constants.Project) && + mapperData.SourceType.IsQueryable(); + } + } +} diff --git a/AgileMapper/DataSources/Finders/MaptimeDataSourceFinder.cs b/AgileMapper/DataSources/Factories/MaptimeDataSourcesFactory.cs similarity index 87% rename from AgileMapper/DataSources/Finders/MaptimeDataSourceFinder.cs rename to AgileMapper/DataSources/Factories/MaptimeDataSourcesFactory.cs index fb0a0c944..34b2a8a93 100644 --- a/AgileMapper/DataSources/Finders/MaptimeDataSourceFinder.cs +++ b/AgileMapper/DataSources/Factories/MaptimeDataSourcesFactory.cs @@ -1,17 +1,16 @@ -namespace AgileObjects.AgileMapper.DataSources.Finders +namespace AgileObjects.AgileMapper.DataSources.Factories { using System.Collections.Generic; using Extensions.Internal; - - internal struct MaptimeDataSourceFinder : IDataSourceFinder + internal static class MaptimeDataSourcesFactory { private static readonly IMaptimeDataSourceFactory[] _mapTimeDataSourceFactories = { default(DictionaryDataSourceFactory) }; - public IEnumerable FindFor(DataSourceFindContext context) + public static IEnumerable Create(DataSourceFindContext context) { if (!UseMaptimeDataSources(context, out var maptimeDataSources)) { diff --git a/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs b/AgileMapper/DataSources/Factories/MetaMemberDataSourcesFactory.cs similarity index 98% rename from AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs rename to AgileMapper/DataSources/Factories/MetaMemberDataSourcesFactory.cs index 6974810d2..ca5df75a2 100644 --- a/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs +++ b/AgileMapper/DataSources/Factories/MetaMemberDataSourcesFactory.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.DataSources.Finders +namespace AgileObjects.AgileMapper.DataSources.Factories { using System; using System.Collections.Generic; @@ -16,11 +16,10 @@ using ObjectPopulation.Enumerables; using ReadableExpressions.Extensions; using TypeConversion; - using static System.StringComparison; - internal struct MetaMemberDataSourceFinder : IDataSourceFinder + internal static class MetaMemberDataSourcesFactory { - public IEnumerable FindFor(DataSourceFindContext context) + public static IEnumerable Create(DataSourceFindContext context) { if (TryGetMetaMemberNameParts(context, out var memberNameParts) && TryGetMetaMember(memberNameParts, context, out var metaMember)) @@ -85,7 +84,7 @@ private static bool TryGetMetaMemberNameParts( default: currentMemberName = memberNamePart + currentMemberName; - if (currentMemberName.StartsWith(NumberOfMetaMemberPart.Name, Ordinal)) + if (currentMemberName.StartsWith(NumberOfMetaMemberPart.Name, StringComparison.Ordinal)) { currentMemberName = currentMemberName.Substring(NumberOfMetaMemberPart.Name.Length); memberNameParts.Add(currentMemberName); diff --git a/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs b/AgileMapper/DataSources/Factories/SourceMemberDataSourcesFactory.cs similarity index 86% rename from AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs rename to AgileMapper/DataSources/Factories/SourceMemberDataSourcesFactory.cs index 988260d43..17d3d7528 100644 --- a/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs +++ b/AgileMapper/DataSources/Factories/SourceMemberDataSourcesFactory.cs @@ -1,12 +1,12 @@ -namespace AgileObjects.AgileMapper.DataSources.Finders +namespace AgileObjects.AgileMapper.DataSources.Factories { using System.Collections.Generic; using Extensions.Internal; using Members; - internal struct SourceMemberDataSourceFinder : IDataSourceFinder + internal static class SourceMemberDataSourcesFactory { - public IEnumerable FindFor(DataSourceFindContext context) + public static IEnumerable Create(DataSourceFindContext context) { if (context.MapperData.TargetMember.IsCustom) { @@ -22,9 +22,9 @@ public IEnumerable FindFor(DataSourceFindContext context) { if (context.DataSourceIndex == 0) { - if (UseFallbackComplexTypeMappingDataSource(targetMember)) + if (UseFallbackComplexTypeDataSource(targetMember)) { - yield return new ComplexTypeMappingDataSource(context.DataSourceIndex, context.ChildMappingData); + yield return ComplexTypeDataSource.Create(context.DataSourceIndex, context.ChildMappingData); } } else if (configuredDataSources.Any() && configuredDataSources.Last().IsConditional) @@ -88,7 +88,7 @@ private static IDataSource GetSourceMemberDataSource( Constants.EmptyExpression); } - private static bool UseFallbackComplexTypeMappingDataSource(QualifiedMember targetMember) + private static bool UseFallbackComplexTypeDataSource(QualifiedMember targetMember) => targetMember.IsComplex && !targetMember.IsDictionary && (targetMember.Type != typeof(object)); } } \ No newline at end of file diff --git a/AgileMapper/DataSources/Finders/DataSourceFinder.cs b/AgileMapper/DataSources/Finders/DataSourceFinder.cs deleted file mode 100644 index e0b08824d..000000000 --- a/AgileMapper/DataSources/Finders/DataSourceFinder.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace AgileObjects.AgileMapper.DataSources.Finders -{ - using System.Collections.Generic; - using System.Linq; - using Members; - - internal struct DataSourceFinder - { - private static readonly IDataSourceFinder[] _finders = - { - default(ConfiguredDataSourceFinder), - default(MaptimeDataSourceFinder), - default(SourceMemberDataSourceFinder), - default(MetaMemberDataSourceFinder) - }; - - public static DataSourceSet FindFor(IChildMemberMappingData childMappingData) - { - var findContext = new DataSourceFindContext(childMappingData); - var validDataSources = EnumerateDataSources(findContext).ToArray(); - - return new DataSourceSet(findContext.MapperData, validDataSources); - } - - private static IEnumerable EnumerateDataSources(DataSourceFindContext context) - { - foreach (var finder in _finders) - { - foreach (var dataSource in finder.FindFor(context)) - { - if (!dataSource.IsValid) - { - continue; - } - - yield return dataSource; - - if (!dataSource.IsConditional) - { - yield break; - } - } - - if (context.StopFind) - { - yield break; - } - } - } - } -} \ No newline at end of file diff --git a/AgileMapper/DataSources/Finders/IDataSourceFinder.cs b/AgileMapper/DataSources/Finders/IDataSourceFinder.cs deleted file mode 100644 index 658e11e98..000000000 --- a/AgileMapper/DataSources/Finders/IDataSourceFinder.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace AgileObjects.AgileMapper.DataSources.Finders -{ - using System.Collections.Generic; - - internal interface IDataSourceFinder - { - IEnumerable FindFor(DataSourceFindContext context); - } -} \ No newline at end of file diff --git a/AgileMapper/DataSources/IDataSource.cs b/AgileMapper/DataSources/IDataSource.cs index 98c72d870..8ae56619f 100644 --- a/AgileMapper/DataSources/IDataSource.cs +++ b/AgileMapper/DataSources/IDataSource.cs @@ -21,10 +21,10 @@ internal interface IDataSource : IConditionallyChainable bool IsFallback { get; } - ICollection Variables { get; } + IList Variables { get; } - Expression AddPreCondition(Expression population); + Expression AddSourceCondition(Expression value); - Expression AddCondition(Expression value, Expression alternateBranch = null); + Expression Finalise(Expression memberPopulation, Expression alternatePopulation = null); } } diff --git a/AgileMapper/DataSources/ValueExpressionBuilders.cs b/AgileMapper/DataSources/ValueExpressionBuilders.cs new file mode 100644 index 000000000..26efa469f --- /dev/null +++ b/AgileMapper/DataSources/ValueExpressionBuilders.cs @@ -0,0 +1,78 @@ +namespace AgileObjects.AgileMapper.DataSources +{ + using System.Collections.Generic; +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif + using Extensions.Internal; + using Members; + + internal static class ValueExpressionBuilders + { + public static Expression SingleDataSource(IList dataSources, IMemberMapperData mapperData) + { + var dataSource = dataSources.Last(); + + var value = dataSource.IsConditional + ? dataSource.Value.ToIfFalseDefaultCondition(dataSource.Condition) + : dataSource.Value; + + return dataSource.AddSourceCondition(value); + } + + public static Expression ConditionTree(IList dataSources, IMemberMapperData mapperData) + { + var value = SingleDataSource(dataSources, mapperData); + + for (var i = dataSources.Count - 2; i >= 0;) + { + var dataSource = dataSources[i--]; + + var dataSourceValue = dataSource.IsConditional + ? Expression.Condition( + dataSource.Condition, + dataSource.Value.GetConversionTo(value.Type), + value) + : dataSource.Value; + + value = dataSource.AddSourceCondition(dataSourceValue); + } + + return value; + } + + public static Expression ValueSequence(IList dataSources, IMemberMapperData mapperData) + { + if (dataSources.HasOne()) + { + return dataSources.First().GetValueSequenceValue(); + } + + var mappingExpressions = dataSources + .ProjectToArray(dataSource => dataSource.GetValueSequenceValue()); + + return Expression.Block(mappingExpressions); + } + + private static Expression GetValueSequenceValue(this IDataSource dataSource) + { + var mapping = dataSource.Value; + + if (dataSource.IsConditional) + { + mapping = Expression.IfThen(dataSource.Condition, mapping); + } + + mapping = dataSource.AddSourceCondition(mapping); + + if (dataSource.Variables.Any()) + { + mapping = Expression.Block(dataSource.Variables, mapping); + } + + return mapping; + } + } +} \ No newline at end of file diff --git a/AgileMapper/Extensions/Internal/EnumerableExtensions.cs b/AgileMapper/Extensions/Internal/EnumerableExtensions.cs index 0d5d4fa60..035733c0b 100644 --- a/AgileMapper/Extensions/Internal/EnumerableExtensions.cs +++ b/AgileMapper/Extensions/Internal/EnumerableExtensions.cs @@ -163,31 +163,6 @@ public static T[] CopyToArray(this IList items) return clonedArray; } - public static Expression ReverseChain(this IList items) - where T : IConditionallyChainable - { - return Chain( - items, - i => i.Last(), - item => item.AddPreConditionIfNecessary(item.Value), - (valueSoFar, item) => item.AddPreConditionIfNecessary( - Expression.Condition(item.Condition, item.Value, valueSoFar)), - i => i.Reverse()); - } - - public static Expression AddPreConditionIfNecessary(this IConditionallyChainable item, Expression ifTrueBranch) - { - if (item.PreCondition == null) - { - return ifTrueBranch; - } - - return Expression.Condition( - item.PreCondition, - ifTrueBranch, - ifTrueBranch.Type.ToDefaultExpression()); - } - public static Expression Chain( this IList items, Func seedValueFactory, @@ -196,8 +171,8 @@ public static Expression Chain( return Chain(items, i => i.First(), seedValueFactory, itemValueFactory, i => i); } - private static Expression Chain( - IList items, + public static Expression Chain( + this IList items, Func, TItem> seedFactory, Func seedValueFactory, Func itemValueFactory, @@ -245,6 +220,12 @@ public static T[] Prepend(this IList items, T initialItem) return newArray; } + public static void Insert(this IList items, T item, int insertionOffset) + { + var insertionIndex = items.Count - insertionOffset; + items.Insert(insertionIndex, item); + } + public static T[] Append(this IList array, T extraItem) { switch (array.Count) diff --git a/AgileMapper/Extensions/Internal/ExpressionExtensions.cs b/AgileMapper/Extensions/Internal/ExpressionExtensions.cs index a3b3d1e76..4e02abc79 100644 --- a/AgileMapper/Extensions/Internal/ExpressionExtensions.cs +++ b/AgileMapper/Extensions/Internal/ExpressionExtensions.cs @@ -5,9 +5,6 @@ using System.Diagnostics; using System.Linq; using System.Reflection; - using NetStandardPolyfills; - using ObjectPopulation.Enumerables; - using ReadableExpressions.Extensions; #if NET35 using Microsoft.Scripting.Ast; using ReadableExpressions.Translations; @@ -17,6 +14,9 @@ using System.Linq.Expressions; using static System.Linq.Expressions.ExpressionType; #endif + using NetStandardPolyfills; + using ObjectPopulation.Enumerables; + using ReadableExpressions.Extensions; internal static partial class ExpressionExtensions { @@ -65,6 +65,10 @@ public static ConstantExpression ToConstantExpression(this TItem item, Ty [DebuggerStepThrough] public static DefaultExpression ToDefaultExpression(this Type type) => Expression.Default(type); + [DebuggerStepThrough] + public static ConditionalExpression ToIfFalseDefaultCondition(this Expression value, Expression condition) + => Expression.Condition(condition, value, value.Type.ToDefaultExpression()); + public static Expression AndTogether(this IList expressions) { if (expressions.None()) diff --git a/AgileMapper/Extensions/Internal/IConditionallyChainable.cs b/AgileMapper/Extensions/Internal/IConditionallyChainable.cs index 894f7c9fc..6c619f709 100644 --- a/AgileMapper/Extensions/Internal/IConditionallyChainable.cs +++ b/AgileMapper/Extensions/Internal/IConditionallyChainable.cs @@ -8,8 +8,6 @@ internal interface IConditionallyChainable { - Expression PreCondition { get; } - Expression Condition { get; } Expression Value { get; } diff --git a/AgileMapper/MappingRuleSet.cs b/AgileMapper/MappingRuleSet.cs index 9d015972e..e8c4e7230 100644 --- a/AgileMapper/MappingRuleSet.cs +++ b/AgileMapper/MappingRuleSet.cs @@ -1,16 +1,16 @@ namespace AgileObjects.AgileMapper { - using DataSources; - using Extensions.Internal; - using Members.Population; - using ObjectPopulation.Enumerables; - using ObjectPopulation.MapperKeys; - using ObjectPopulation.RepeatedMappings; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using DataSources.Factories; + using Extensions.Internal; + using Members.Population; + using ObjectPopulation.Enumerables; + using ObjectPopulation.MapperKeys; + using ObjectPopulation.RepeatedMappings; internal class MappingRuleSet { diff --git a/AgileMapper/MappingRuleSetCollection.cs b/AgileMapper/MappingRuleSetCollection.cs index 8b9b91372..d61223f9e 100644 --- a/AgileMapper/MappingRuleSetCollection.cs +++ b/AgileMapper/MappingRuleSetCollection.cs @@ -1,7 +1,7 @@ namespace AgileObjects.AgileMapper { using System.Collections.Generic; - using DataSources; + using DataSources.Factories; using Extensions.Internal; using Members.Population; using ObjectPopulation.Enumerables; diff --git a/AgileMapper/Members/MemberMapperDataExtensions.cs b/AgileMapper/Members/MemberMapperDataExtensions.cs index 2ea9abd07..4d08d9ef8 100644 --- a/AgileMapper/Members/MemberMapperDataExtensions.cs +++ b/AgileMapper/Members/MemberMapperDataExtensions.cs @@ -12,6 +12,7 @@ namespace AgileObjects.AgileMapper.Members using System.Reflection; using Configuration; using DataSources; + using DataSources.Factories; using Dictionaries; using Extensions; using Extensions.Internal; diff --git a/AgileMapper/Members/Population/MemberPopulator.cs b/AgileMapper/Members/Population/MemberPopulator.cs index 3776cee57..fcb554a23 100644 --- a/AgileMapper/Members/Population/MemberPopulator.cs +++ b/AgileMapper/Members/Population/MemberPopulator.cs @@ -69,10 +69,10 @@ private static DataSourceSet CreateNullDataSourceSet( IMemberMapperData mapperData, Func commentFactory) { - return new DataSourceSet( - mapperData, + return DataSourceSet.For( new NullDataSource( - ReadableExpression.Comment(commentFactory.Invoke(mapperData.TargetMember)))); + ReadableExpression.Comment(commentFactory.Invoke(mapperData.TargetMember))), + mapperData); } #endregion @@ -87,7 +87,7 @@ public Expression GetPopulation() { if (!CanPopulate) { - return _dataSources.ValueExpression; + return _dataSources.BuildValue(); } var populationGuard = MapperData @@ -120,14 +120,11 @@ public Expression GetPopulation() private Expression GetBinding(Expression populationGuard) { - var bindingValue = _dataSources.ValueExpression; + var bindingValue = _dataSources.BuildValue(); if (MapperData.RuleSet.Settings.AllowGuardedBindings && (populationGuard != null)) { - bindingValue = Expression.Condition( - populationGuard, - bindingValue, - bindingValue.Type.ToDefaultExpression()); + bindingValue = bindingValue.ToIfFalseDefaultCondition(populationGuard); } return MapperData.GetTargetMemberPopulation(bindingValue); @@ -138,7 +135,7 @@ private Expression GetReadOnlyMemberPopulation() var targetMemberAccess = MapperData.GetTargetMemberAccess(); var targetMemberNotNull = targetMemberAccess.GetIsNotDefaultComparison(); - return Expression.IfThen(targetMemberNotNull, _dataSources.ValueExpression); + return Expression.IfThen(targetMemberNotNull, _dataSources.BuildValue()); } private Expression GetPopulationExpression() @@ -161,20 +158,12 @@ private Expression GetPopulationExpression() } population = MapperData.GetTargetMemberPopulation(finalValue); - - if (dataSource.IsConditional) - { - population = dataSource.AddCondition(population); - } - - population = dataSource.AddPreCondition(population); + population = dataSource.Finalise(population); continue; } var memberPopulation = MapperData.GetTargetMemberPopulation(dataSource.Value); - - population = dataSource.AddCondition(memberPopulation, population); - population = dataSource.AddPreCondition(population); + population = dataSource.Finalise(memberPopulation, population); } return population; @@ -188,14 +177,10 @@ private Expression GetPopulationWithVariables(Expression population) } var populationBlock = (BlockExpression)population; - var variables = _dataSources.Variables; - - if (Enumerable.Any(populationBlock.Variables)) - { - variables = variables.Append(populationBlock.Variables); - } - return Expression.Block(variables, populationBlock.Expressions); + return Expression.Block( + _dataSources.Variables.Append(populationBlock.Variables), + populationBlock.Expressions); } #region ExcludeFromCodeCoverage diff --git a/AgileMapper/Members/Population/MemberPopulatorFactory.cs b/AgileMapper/Members/Population/MemberPopulatorFactory.cs index 74e2ef23b..03a788356 100644 --- a/AgileMapper/Members/Population/MemberPopulatorFactory.cs +++ b/AgileMapper/Members/Population/MemberPopulatorFactory.cs @@ -8,7 +8,7 @@ namespace AgileObjects.AgileMapper.Members.Population using System.Linq.Expressions; #endif using Configuration; - using DataSources.Finders; + using DataSources.Factories; using Extensions; using Extensions.Internal; using Members; @@ -66,7 +66,7 @@ private static IMemberPopulator Create(QualifiedMember targetMember, IObjectMapp } var childMappingData = mappingData.GetChildMappingData(childMapperData); - var dataSources = DataSourceFinder.FindFor(childMappingData); + var dataSources = DataSourceSetFactory.CreateFor(childMappingData); if (dataSources.None) { diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs index 77c60c13f..ab692448c 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs @@ -12,7 +12,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes using Caching; using Configuration; using DataSources; - using DataSources.Finders; + using DataSources.Factories; using Extensions; using Extensions.Internal; using MapperKeys; @@ -359,7 +359,7 @@ private static DataSourceSet[] GetArgumentDataSources(TInvokable invokable, Cons key.MappingData.MapperData); var memberMappingData = key.MappingData.GetChildMappingData(parameterMapperData); - var dataSources = DataSourceFinder.FindFor(memberMappingData); + var dataSources = DataSourceSetFactory.CreateFor(memberMappingData); return dataSources; }); @@ -467,7 +467,7 @@ public ConstructionData(ConstructionDataInfo info) } } - argumentValues.Add(dataSources.ValueExpression); + argumentValues.Add(dataSources.BuildValue()); if (info.IsUnconditional) { @@ -513,8 +513,9 @@ private static Expression BuildConditions(DataSourceSet dataSources) public Construction Construction { get; } } - private class Construction : IConditionallyChainable + private class Construction { + private readonly Expression _condition; private readonly Expression _construction; private ParameterExpression _mappingDataObject; @@ -524,29 +525,41 @@ public Construction(ConfiguredObjectFactory configuredFactory, IMemberMapperData UsesMappingDataObjectParameter = configuredFactory.UsesMappingDataObjectParameter; } - private Construction(IList constructions) - : this(constructions.ReverseChain()) - { - UsesMappingDataObjectParameter = constructions.Any(c => c.UsesMappingDataObjectParameter); - } - - public Construction(Expression construction, Expression condition = null) + public Construction( + Expression construction, + Expression condition = null, + bool usesMappingDataObjectParameter = false) { _construction = construction; - Condition = condition; + _condition = condition; + UsesMappingDataObjectParameter = usesMappingDataObjectParameter; } #region Factory Methods public static Construction For(IList constructions, ConstructionKey key) { - var construction = constructions.HasOne() - ? constructions.First() - : new Construction(constructions); + if (constructions.HasOne()) + { + return constructions.First().With(key); + } + + var construction = new Construction( + ReverseChain(constructions), + usesMappingDataObjectParameter: constructions.Any(c => c.UsesMappingDataObjectParameter)); return construction.With(key); } + private static Expression ReverseChain(IList constructions) + { + return constructions.Chain( + cs => cs.Last(), + item => item._construction, + (valueSoFar, item) => Expression.Condition(item._condition, item._construction, valueSoFar), + i => i.Reverse()); + } + public Construction With(ConstructionKey key) { _mappingDataObject = key.MappingData.MapperData.MappingDataObject; @@ -555,12 +568,6 @@ public Construction With(ConstructionKey key) #endregion - public Expression PreCondition => null; - - public Expression Condition { get; } - - Expression IConditionallyChainable.Value => _construction; - public bool UsesMappingDataObjectParameter { get; } public Expression GetConstruction(IObjectMappingData mappingData) diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs index a6cd8fbc2..b3c26b330 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs @@ -1,11 +1,14 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes { using System.Collections.Generic; + using System.Linq; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using DataSources; + using DataSources.Factories; using Extensions.Internal; using Members; using NetStandardPolyfills; @@ -19,7 +22,7 @@ internal class ComplexTypeMappingExpressionFactory : MappingExpressionFactoryBas private readonly PopulationExpressionFactoryBase _multiStatementPopulationFactory; private readonly IList _shortCircuitFactories; - private ComplexTypeMappingExpressionFactory() + public ComplexTypeMappingExpressionFactory() { _memberInitPopulationFactory = new MemberInitPopulationExpressionFactory(); _multiStatementPopulationFactory = new MultiStatementPopulationExpressionFactory(); @@ -30,8 +33,6 @@ private ComplexTypeMappingExpressionFactory() }; } - public override bool IsFor(IObjectMappingData mappingData) => true; - protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out string reason) { if (mappingData.MapperData.TargetCouldBePopulated()) @@ -71,12 +72,60 @@ private static bool DerivedTypesExistForTarget(IMemberMapperData mapperData) #region Short-Circuits - protected override IEnumerable GetShortCircuitReturns(GotoExpression returnNull, IObjectMappingData mappingData) + protected override bool ShortCircuitMapping(MappingCreationContext context, out Expression mapping) + { + var derivedTypeDataSources = DerivedComplexTypeDataSourcesFactory.CreateFor(context.MappingData); + + if (derivedTypeDataSources.None()) + { + return base.ShortCircuitMapping(context, out mapping); + } + + var derivedTypeDataSourceSet = DataSourceSet.For( + derivedTypeDataSources, + context.MapperData, + ValueExpressionBuilders.ValueSequence); + + mapping = derivedTypeDataSourceSet.BuildValue(); + + if (derivedTypeDataSources.Last().IsConditional) + { + context.MappingExpressions.Add(mapping); + return false; + } + + var shortCircuitReturns = GetShortCircuitReturns(context.MappingData).ToArray(); + + if (shortCircuitReturns.Any()) + { + context.MappingExpressions.AddRange(shortCircuitReturns); + } + + if (mapping.NodeType == ExpressionType.Goto) + { + mapping = ((GotoExpression)mapping).Value; + context.MappingExpressions.Add(context.MapperData.GetReturnLabel(mapping)); + } + else + { + context.MappingExpressions.Add(mapping); + context.MappingExpressions.Add(context.MapperData.GetReturnLabel(mapping.Type.ToDefaultExpression())); + } + + mapping = Expression.Block(context.MappingExpressions); + return true; + } + + protected override IEnumerable GetShortCircuitReturns(IObjectMappingData mappingData) { var mapperData = mappingData.MapperData; if (SourceObjectCouldBeNull(mapperData)) { + var returnNull = Expression.Return( + mapperData.ReturnLabelTarget, + mapperData.TargetType.ToDefaultExpression()); + yield return Expression.IfThen(mapperData.SourceObject.GetIsDefaultComparison(), returnNull); } @@ -145,9 +194,6 @@ private bool TryGetShortCircuitFactory(ObjectMapperData mapperData, out ISourceS #endregion - protected override Expression GetDerivedTypeMappings(IObjectMappingData mappingData) - => DerivedComplexTypeMappingsFactory.CreateFor(mappingData); - protected override IEnumerable GetObjectPopulation(MappingCreationContext context) { var expressionFactory = context.MapperData.UseMemberInitialisations() diff --git a/AgileMapper/ObjectPopulation/DerivedMappingFactory.cs b/AgileMapper/ObjectPopulation/DerivedMappingFactory.cs index 3b2cb6a87..62b297d3c 100644 --- a/AgileMapper/ObjectPopulation/DerivedMappingFactory.cs +++ b/AgileMapper/ObjectPopulation/DerivedMappingFactory.cs @@ -16,14 +16,27 @@ public static Expression GetDerivedTypeMapping( Expression sourceValue, Type targetType) { + return GetDerivedTypeMapping( + declaredTypeMappingData, + sourceValue, + targetType, + out _); + } + + public static Expression GetDerivedTypeMapping( + IObjectMappingData declaredTypeMappingData, + Expression sourceValue, + Type targetType, + out IObjectMappingData derivedTypeMappingData) + { + derivedTypeMappingData = declaredTypeMappingData.WithTypes(sourceValue.Type, targetType); + var declaredTypeMapperData = declaredTypeMappingData.MapperData; var targetValue = declaredTypeMapperData.TargetMember.IsReadable ? declaredTypeMapperData.TargetObject.GetConversionTo(targetType) : targetType.ToDefaultExpression(); - var derivedTypeMappingData = declaredTypeMappingData.WithTypes(sourceValue.Type, targetType); - if (declaredTypeMappingData.IsRoot) { return GetDerivedTypeRootMapping(derivedTypeMappingData, sourceValue, targetValue); diff --git a/AgileMapper/ObjectPopulation/DerivedSourceTypeCheck.cs b/AgileMapper/ObjectPopulation/DerivedSourceTypeCheck.cs index 23f7b532d..a8d4734b4 100644 --- a/AgileMapper/ObjectPopulation/DerivedSourceTypeCheck.cs +++ b/AgileMapper/ObjectPopulation/DerivedSourceTypeCheck.cs @@ -1,26 +1,26 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { using System; - using Extensions.Internal; - using Members; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; + using Members; internal class DerivedSourceTypeCheck { - private readonly Type _derivedSourceType; - public DerivedSourceTypeCheck(Type derivedSourceType) { - _derivedSourceType = derivedSourceType; + DerivedSourceType = derivedSourceType; var typedVariableName = "source" + derivedSourceType.GetVariableNameInPascalCase(); TypedVariable = Expression.Variable(derivedSourceType, typedVariableName); } + public Type DerivedSourceType { get; } + public ParameterExpression TypedVariable { get; } public Expression GetTypedVariableAssignment(IMemberMapperData declaredTypeMapperData) @@ -28,7 +28,7 @@ public Expression GetTypedVariableAssignment(IMemberMapperData declaredTypeMappe public Expression GetTypedVariableAssignment(Expression sourceObject) { - var typeAsConversion = Expression.TypeAs(sourceObject, _derivedSourceType); + var typeAsConversion = Expression.TypeAs(sourceObject, DerivedSourceType); var typedVariableAssignment = TypedVariable.AssignTo(typeAsConversion); return typedVariableAssignment; diff --git a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs index f84553f4c..5a4df4d02 100644 --- a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs @@ -10,7 +10,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation #endif using System.Reflection; using ComplexTypes; - using DataSources; + using DataSources.Factories; using Enumerables.Dictionaries; using Extensions; using Extensions.Internal; @@ -22,11 +22,9 @@ namespace AgileObjects.AgileMapper.ObjectPopulation internal class DictionaryMappingExpressionFactory : MappingExpressionFactoryBase { - public static readonly MappingExpressionFactoryBase Instance = new DictionaryMappingExpressionFactory(); - private readonly MemberPopulatorFactory _memberPopulatorFactory; - private DictionaryMappingExpressionFactory() + public DictionaryMappingExpressionFactory() { _memberPopulatorFactory = new MemberPopulatorFactory(GetAllTargetMembers); } @@ -228,31 +226,6 @@ private static QualifiedMember[] GetConfiguredTargetMembers( #endregion - public override bool IsFor(IObjectMappingData mappingData) - { - if (mappingData.MapperData.TargetMember.IsDictionary) - { - return true; - } - - if (mappingData.IsRoot) - { - return false; - } - - if (!(mappingData.MapperData.TargetMember is DictionaryTargetMember dictionaryMember)) - { - return false; - } - - if (dictionaryMember.HasSimpleEntries) - { - return true; - } - - return dictionaryMember.HasObjectEntries && !mappingData.IsStandalone(); - } - protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out string reason) { if (mappingData.MappingTypes.SourceType.IsDictionary()) @@ -300,7 +273,7 @@ protected override IEnumerable GetObjectPopulation(MappingCreationCo yield return assignment; } - ReturnPopulation: + ReturnPopulation: if (population != null) { yield return population; diff --git a/AgileMapper/ObjectPopulation/EnumMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/EnumMappingExpressionFactory.cs index 85a3497cf..90e680005 100644 --- a/AgileMapper/ObjectPopulation/EnumMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/EnumMappingExpressionFactory.cs @@ -2,23 +2,17 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { using System.Collections.Generic; using System.Globalization; - using Extensions.Internal; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; using Members; - using NetStandardPolyfills; using ReadableExpressions.Extensions; internal class EnumMappingExpressionFactory : MappingExpressionFactoryBase { - public static readonly EnumMappingExpressionFactory Instance = new EnumMappingExpressionFactory(); - - public override bool IsFor(IObjectMappingData mappingData) - => mappingData.MapperData.TargetType.GetNonNullableType().IsEnum(); - protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out string reason) { var mapperData = mappingData.MapperData; diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs index df9c9545a..860b6c014 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs @@ -259,7 +259,7 @@ private Expression GetPopulation( var sourceMember = mappingData.MapperData.SourceMember; var mappingDataSource = new AdHocDataSource(sourceMember, elementMapping); - var mappingDataSources = new DataSourceSet(elementMapperData, mappingDataSource); + var mappingDataSources = DataSourceSet.For(mappingDataSource, elementMapperData); var populationExpression = MemberPopulator .WithoutRegistration(mappingDataSources) diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs index cc40bd781..6a6255bcd 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs @@ -1,12 +1,12 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables.Dictionaries { - using DataSources; - using Extensions.Internal; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using DataSources; + using Extensions.Internal; internal class SourceObjectDictionaryPopulationLoopData : IPopulationLoopData { @@ -85,12 +85,7 @@ public static BinaryExpression GetSourceEnumerableFoundTest( } private Expression GetEnumeratorIfNecessary(Expression getEnumeratorCall) - { - return Expression.Condition( - _sourceEnumerableFound, - getEnumeratorCall, - getEnumeratorCall.Type.ToDefaultExpression()); - } + => getEnumeratorCall.ToIfFalseDefaultCondition(_sourceEnumerableFound); private Expression DisposeEnumeratorIfNecessary(Expression disposeEnumeratorCall) => Expression.IfThen(_sourceEnumerableFound, disposeEnumeratorCall); diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerableMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerableMappingExpressionFactory.cs index e368156ca..b4d324b86 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerableMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerableMappingExpressionFactory.cs @@ -1,21 +1,16 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables { using System.Collections.Generic; - using Extensions.Internal; - using Members; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; + using Members; internal class EnumerableMappingExpressionFactory : MappingExpressionFactoryBase { - public static readonly MappingExpressionFactoryBase Instance = new EnumerableMappingExpressionFactory(); - - public override bool IsFor(IObjectMappingData mappingData) - => mappingData.MapperData.TargetMember.IsEnumerable; - protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out string reason) { var mapperData = mappingData.MapperData; diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs index 933a8e2d2..92cce4468 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs @@ -4,17 +4,17 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif using Caching; using Extensions; using Extensions.Internal; using Members; using NetStandardPolyfills; using ReadableExpressions.Extensions; -#if NET35 - using Microsoft.Scripting.Ast; -#else - using System.Linq.Expressions; -#endif internal class EnumerablePopulationBuilder { diff --git a/AgileMapper/ObjectPopulation/MappingCreationContext.cs b/AgileMapper/ObjectPopulation/MappingCreationContext.cs index 0e943913b..7221ab50e 100644 --- a/AgileMapper/ObjectPopulation/MappingCreationContext.cs +++ b/AgileMapper/ObjectPopulation/MappingCreationContext.cs @@ -21,22 +21,20 @@ internal class MappingCreationContext public MappingCreationContext(IObjectMappingData mappingData) { - var mapperData = mappingData.MapperData; - MappingData = mappingData; - MapToNullCondition = GetMapToNullConditionOrNull(mapperData); + MapToNullCondition = GetMapToNullConditionOrNull(MapperData); InstantiateLocalVariable = true; MappingExpressions = new List(); - if (mapperData.RuleSet.Settings.UseSingleRootMappingExpression) + if (RuleSet.Settings.UseSingleRootMappingExpression) { return; } - var basicMapperData = mapperData.WithNoTargetMember(); + var basicMapperData = MapperData.WithNoTargetMember(); - PreMappingCallback = basicMapperData.GetMappingCallbackOrNull(Before, mapperData); - PostMappingCallback = basicMapperData.GetMappingCallbackOrNull(After, mapperData); + PreMappingCallback = basicMapperData.GetMappingCallbackOrNull(Before, MapperData); + PostMappingCallback = basicMapperData.GetMappingCallbackOrNull(After, MapperData); } private static Expression GetMapToNullConditionOrNull(IMemberMapperData mapperData) diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index a73872320..9a51e79f5 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -5,8 +5,10 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using System.Linq; #if NET35 using Microsoft.Scripting.Ast; + using static Microsoft.Scripting.Ast.ExpressionType; #else using System.Linq.Expressions; + using static System.Linq.Expressions.ExpressionType; #endif using DataSources; using Extensions; @@ -15,16 +17,9 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using NetStandardPolyfills; using ReadableExpressions; using ReadableExpressions.Extensions; -#if NET35 - using static Microsoft.Scripting.Ast.ExpressionType; -#else - using static System.Linq.Expressions.ExpressionType; -#endif internal abstract class MappingExpressionFactoryBase { - public abstract bool IsFor(IObjectMappingData mappingData); - public Expression Create(IObjectMappingData mappingData) { var mapperData = mappingData.MapperData; @@ -36,23 +31,13 @@ public Expression Create(IObjectMappingData mappingData) GetNullMappingFallbackValue(mapperData)); } - var returnNull = Expression.Return( - mapperData.ReturnLabelTarget, - mapperData.TargetType.ToDefaultExpression()); + var context = new MappingCreationContext(mappingData); - if (MappingAlwaysBranchesToDerivedType(mappingData, out var derivedTypeMappings)) + if (ShortCircuitMapping(context, out var mapping)) { - var shortCircuitReturns = GetShortCircuitReturns(returnNull, mappingData).ToArray(); - - return shortCircuitReturns.Any() - ? Expression.Block(shortCircuitReturns.Append(derivedTypeMappings)) - : derivedTypeMappings; + return mapping; } - var context = new MappingCreationContext(mappingData); - - context.MappingExpressions.AddUnlessNullOrEmpty(derivedTypeMappings); - AddPopulationsAndCallbacks(context); if (NothingIsBeingMapped(context)) @@ -60,7 +45,7 @@ public Expression Create(IObjectMappingData mappingData) return mapperData.IsEntryPoint ? mapperData.TargetObject : Constants.EmptyExpression; } - context.MappingExpressions.InsertRange(0, GetShortCircuitReturns(returnNull, mappingData)); + context.MappingExpressions.InsertRange(0, GetShortCircuitReturns(mappingData)); var mappingBlock = GetMappingBlock(context); @@ -81,23 +66,13 @@ protected virtual bool TargetCannotBeMapped(IObjectMappingData mappingData, out protected virtual Expression GetNullMappingFallbackValue(IMemberMapperData mapperData) => mapperData.TargetType.ToDefaultExpression(); - private bool MappingAlwaysBranchesToDerivedType(IObjectMappingData mappingData, out Expression derivedTypeMappings) + protected virtual bool ShortCircuitMapping(MappingCreationContext context, out Expression mapping) { - derivedTypeMappings = GetDerivedTypeMappings(mappingData); - - if (derivedTypeMappings.NodeType != Goto) - { - return false; - } - - var returnExpression = (GotoExpression)derivedTypeMappings; - derivedTypeMappings = returnExpression.Value; - return true; + mapping = null; + return false; } - protected virtual Expression GetDerivedTypeMappings(IObjectMappingData mappingData) => Constants.EmptyExpression; - - protected virtual IEnumerable GetShortCircuitReturns(GotoExpression returnNull, IObjectMappingData mappingData) + protected virtual IEnumerable GetShortCircuitReturns(IObjectMappingData mappingData) => Enumerable.Empty; private void AddPopulationsAndCallbacks(MappingCreationContext context) diff --git a/AgileMapper/ObjectPopulation/MappingFactory.cs b/AgileMapper/ObjectPopulation/MappingFactory.cs index 7ce36d73a..160c87163 100644 --- a/AgileMapper/ObjectPopulation/MappingFactory.cs +++ b/AgileMapper/ObjectPopulation/MappingFactory.cs @@ -1,13 +1,13 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { - using Extensions; - using Extensions.Internal; - using Members; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions; + using Extensions.Internal; + using Members; internal static class MappingFactory { @@ -18,6 +18,13 @@ public static Expression GetChildMapping( IChildMemberMappingData childMappingData) { var childMapperData = childMappingData.MapperData; + + var childObjectMappingData = ObjectMappingDataFactory.ForChild( + sourceMember, + childMapperData.TargetMember, + dataSourceIndex, + childMappingData.Parent); + var targetMemberAccess = childMapperData.GetTargetMemberAccess(); childMapperData.TargetMember.MapCreating(sourceMember.Type); @@ -27,12 +34,6 @@ public static Expression GetChildMapping( targetMemberAccess, childMapperData.EnumerableIndex); - var childObjectMappingData = ObjectMappingDataFactory.ForChild( - sourceMember, - childMapperData.TargetMember, - dataSourceIndex, - childMappingData.Parent); - if (childObjectMappingData.MappingTypes.RuntimeTypesNeeded) { return childMapperData.Parent.GetRuntimeTypedMapping( @@ -142,7 +143,9 @@ public static Expression GetElementMapping( targetElementValue, enumerableIndex); - elementMapperData.Context.IsForNewElement = targetElementValue.NodeType == ExpressionType.Default; + elementMapperData.Context.IsForNewElement = + (targetElementValue.NodeType == ExpressionType.Default) || + (elementMapperData.DeclaredTypeMapperData?.Context.IsForNewElement == true); if (elementMapperData.IsRepeatMapping && elementMapperData.RuleSet.RepeatMappingStrategy.AppliesTo(elementMapperData)) @@ -249,8 +252,6 @@ private static bool ShouldUseLocalSourceValueVariable( SourceAccessCounter.MultipleAccessesExist(sourceValue, mapping); } - - public static Expression UseLocalSourceValueVariableIfAppropriate( Expression mappingExpression, ObjectMapperData mapperData) diff --git a/AgileMapper/ObjectPopulation/ObjectMapperFactory.cs b/AgileMapper/ObjectPopulation/ObjectMapperFactory.cs index be8a20a6a..c949adde4 100644 --- a/AgileMapper/ObjectPopulation/ObjectMapperFactory.cs +++ b/AgileMapper/ObjectPopulation/ObjectMapperFactory.cs @@ -2,35 +2,22 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { using System; using System.Collections.Generic; - using Caching; - using ComplexTypes; - using Enumerables; - using Extensions.Internal; - using MapperKeys; - using Queryables; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Caching; + using DataSources.Factories; + using MapperKeys; internal class ObjectMapperFactory { - private readonly IList _mappingExpressionFactories; private readonly ICache _rootMappersCache; private Dictionary> _creationCallbacksByKey; public ObjectMapperFactory(CacheSet mapperScopedCacheSet) { - _mappingExpressionFactories = new[] - { - QueryProjectionExpressionFactory.Instance, - EnumMappingExpressionFactory.Instance, - DictionaryMappingExpressionFactory.Instance, - EnumerableMappingExpressionFactory.Instance, - ComplexTypeMappingExpressionFactory.Instance - }; - _rootMappersCache = mapperScopedCacheSet.CreateScoped(default(RootMapperKeyComparer)); } @@ -65,10 +52,9 @@ public ObjectMapper GetOrCreateRoot(ObjectMa public ObjectMapper Create(ObjectMappingData mappingData) { - var mappingExpressionFactory = _mappingExpressionFactories.First(mef => mef.IsFor(mappingData)); - var mappingExpression = mappingExpressionFactory.Create(mappingData); + var mappingExpression = DataSourceSetFactory.CreateFor(mappingData).BuildValue(); - if (mappingExpression.NodeType == ExpressionType.Default) + if (mappingExpression == Constants.EmptyExpression) { return null; } diff --git a/AgileMapper/ObjectPopulation/ObjectMappingData.cs b/AgileMapper/ObjectPopulation/ObjectMappingData.cs index f21e16dfd..76fe8be37 100644 --- a/AgileMapper/ObjectPopulation/ObjectMappingData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMappingData.cs @@ -2,17 +2,17 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { using System; using System.Collections.Generic; +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif using Caching; using Extensions.Internal; using MapperKeys; using Members; using NetStandardPolyfills; using Validation; -#if NET35 - using Microsoft.Scripting.Ast; -#else - using System.Linq.Expressions; -#endif internal class ObjectMappingData : MappingInstanceData, diff --git a/AgileMapper/Queryables/QueryProjectionExpressionFactory.cs b/AgileMapper/Queryables/QueryProjectionExpressionFactory.cs index eca899539..168d6213e 100644 --- a/AgileMapper/Queryables/QueryProjectionExpressionFactory.cs +++ b/AgileMapper/Queryables/QueryProjectionExpressionFactory.cs @@ -11,18 +11,6 @@ internal class QueryProjectionExpressionFactory : MappingExpressionFactoryBase { - public static readonly MappingExpressionFactoryBase Instance = new QueryProjectionExpressionFactory(); - - public override bool IsFor(IObjectMappingData mappingData) - { - var mapperData = mappingData.MapperData; - - return mapperData.IsRoot && - mapperData.TargetMember.IsEnumerable && - (mappingData.MappingContext.RuleSet.Name == Constants.Project) && - mapperData.SourceType.IsQueryable(); - } - protected override IEnumerable GetObjectPopulation(MappingCreationContext context) { var mapperData = context.MapperData; diff --git a/AgileMapper/TypeConversion/ToBoolConverter.cs b/AgileMapper/TypeConversion/ToBoolConverter.cs index 32da2d8cd..9e8135d9e 100644 --- a/AgileMapper/TypeConversion/ToBoolConverter.cs +++ b/AgileMapper/TypeConversion/ToBoolConverter.cs @@ -45,10 +45,7 @@ public Expression GetConversion(Expression sourceValue, Type targetType) var sourceValueConversion = Expression.Condition( sourceEqualsTrueTests, true.ToConstantExpression(typeof(bool?)), - Expression.Condition( - sourceEqualsFalseTests, - false.ToConstantExpression(typeof(bool?)), - typeof(bool?).ToDefaultExpression())); + false.ToConstantExpression(typeof(bool?)).ToIfFalseDefaultCondition(sourceEqualsFalseTests)); return sourceValueConversion; } diff --git a/AgileMapper/TypeConversion/ToNumericConverter.cs b/AgileMapper/TypeConversion/ToNumericConverter.cs index 6f20a9e4b..bfe885644 100644 --- a/AgileMapper/TypeConversion/ToNumericConverter.cs +++ b/AgileMapper/TypeConversion/ToNumericConverter.cs @@ -15,7 +15,7 @@ internal class ToNumericConverter : TryParseConverter { #region Cached Items - public new static readonly ToNumericConverter Instance = new ToNumericConverter(); + public static new readonly ToNumericConverter Instance = new ToNumericConverter(); private static readonly Type[] _coercibleNumericTypes = typeof(TNumeric).GetCoercibleNumericTypes(); @@ -75,26 +75,19 @@ private static Type GetNonEnumSourceType(Expression sourceValue) private static Expression GetBoolToNumericConversion(Expression sourceValue, Type targetType) { - var sourceIsNotNullable = sourceValue.Type == typeof(bool); - - var testValue = sourceIsNotNullable - ? sourceValue - : sourceValue.GetConversionTo(); + var sourceIsNullable = sourceValue.Type != typeof(bool); var boolConversion = Expression.Condition( - testValue, + sourceIsNullable ? sourceValue.GetConversionTo() : sourceValue, One.GetConversionTo(targetType), Zero.GetConversionTo(targetType)); - if (sourceIsNotNullable) + if (sourceIsNullable) { - return boolConversion; + boolConversion = boolConversion.ToIfFalseDefaultCondition(sourceValue.GetIsNotDefaultComparison()); } - return Expression.Condition( - sourceValue.GetIsNotDefaultComparison(), - boolConversion, - boolConversion.Type.ToDefaultExpression()); + return boolConversion; } private static bool IsCoercible(Type sourceType) => _coercibleNumericTypes.Contains(sourceType); diff --git a/AgileMapper/TypeConversion/ToStringConverter.cs b/AgileMapper/TypeConversion/ToStringConverter.cs index d9a29412c..78c9dda64 100644 --- a/AgileMapper/TypeConversion/ToStringConverter.cs +++ b/AgileMapper/TypeConversion/ToStringConverter.cs @@ -118,10 +118,8 @@ private static Expression GetBoolToStringConversion(Expression sourceValue, Type return GetTrueOrFalseTernary(sourceValue); } - var nullTrueOrFalse = Expression.Condition( - Expression.Property(sourceValue, "HasValue"), - GetTrueOrFalseTernary(sourceValue.GetNullableValueAccess()), - typeof(string).ToDefaultExpression()); + var nullTrueOrFalse = GetTrueOrFalseTernary(sourceValue.GetNullableValueAccess()) + .ToIfFalseDefaultCondition(Expression.Property(sourceValue, "HasValue")); return nullTrueOrFalse; }