Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions AgileMapper.UnitTests/Configuration/WhenConfiguringDerivedTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,37 @@ public void ShouldUseATypedToTarget()
}
}

// See https://github.com/agileobjects/AgileMapper/issues/129
[Fact]
public void ShouldUseAConfiguredCtorParameterWithATypedToTarget()
{
using (var mapper = Mapper.CreateNew())
{
mapper.WhenMapping
.From<Issue129.Source.SituationObject>()
.To<Issue129.Target.SituationObject>()
.Map(d => d.Source.CurrentClass)
.ToCtor<Issue129.Target.SituationClass>();

mapper.WhenMapping
.From<Issue129.Source.ActionObject>()
.To<Issue129.Target.ActionObject>()
.Map(new Issue129.Target.ActionClass())
.ToCtor<Issue129.Target.ActionClass>();

mapper.WhenMapping
.From<Issue129.Source.Wrapper>()
.To<Issue129.Target.ITrafficObj>()
.If(d => d.Source.ConcreteValue == Issue129.Source.Wrapper.ConcreteValueType.Action)
.Map(d => d.Source.ActionValue)
.ToTarget<Issue129.Target.ActionObject>()
.And
.If(d => d.Source.ConcreteValue == Issue129.Source.Wrapper.ConcreteValueType.Situation)
.Map(d => d.Source.SituationValue)
.ToTarget<Issue129.Target.SituationObject>();
}
}

#region Helper Classes

internal class Issue123
Expand Down Expand Up @@ -303,6 +334,65 @@ public class Leaf : ILeaf
}
}

internal class Issue129
{
public static class Source
{
public class SituationClass
{
public string Name { get; set; }
}

public class SituationObject
{
public string Name { get; set; }

public SituationClass CurrentClass { get; set; }

}
public class ActionObject
{
public string Name { get; set; }
}

public class Wrapper
{
public enum ConcreteValueType { Situation, Action }

public ConcreteValueType ConcreteValue { get; set; }

public SituationObject SituationValue { get; set; }

public ActionObject ActionValue { get; set; }
}
}

public static class Target
{
public interface ITrafficObj { ITrafficClass CurrentClass { get; } }

public interface ITrafficClass { string Name { get; } }

public class SituationClass : ITrafficClass { public string Name { get; set; } }

public class ActionClass : ITrafficClass { public string Name { get; set; } }

public class SituationObject : ITrafficObj
{
public SituationObject(SituationClass clazz) { CurrentClass = clazz; }

public ITrafficClass CurrentClass { get; private set; }
}

public class ActionObject : ITrafficObj
{
public ActionObject(ActionClass clazz) { CurrentClass = clazz; }

public ITrafficClass CurrentClass { get; private set; }
}
}
}

#endregion
}
}
60 changes: 51 additions & 9 deletions AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
namespace AgileObjects.AgileMapper.Api.Configuration
{
using System;
using AgileMapper.Configuration;
using Extensions.Internal;
using NetStandardPolyfills;
using ObjectPopulation;
using Projection;
using ReadableExpressions.Extensions;

Expand All @@ -17,6 +19,8 @@ public DerivedPairTargetTypeSpecifier(MappingConfigInfo configInfo)
_configInfo = configInfo;
}

private MapperContext MapperContext => _configInfo.MapperContext;

public IMappingConfigContinuation<TSource, TTarget> To<TDerivedTarget>()
where TDerivedTarget : TTarget
{
Expand All @@ -33,7 +37,7 @@ private MappingConfigContinuation<TSource, TTarget> SetDerivedTargetType<TDerive
var derivedTypePair = DerivedTypePair
.For<TDerivedSource, TTarget, TDerivedTarget>(_configInfo);

_configInfo.MapperContext.UserConfigurations.DerivedTypes.Add(derivedTypePair);
MapperContext.UserConfigurations.DerivedTypes.Add(derivedTypePair);

return new MappingConfigContinuation<TSource, TTarget>(_configInfo);
}
Expand All @@ -42,12 +46,12 @@ private void ThrowIfUnconstructable<TDerivedTarget>()
{
var mappingData = _configInfo.ToMappingData<TSource, TDerivedTarget>();

var objectCreation = _configInfo
.MapperContext
.ConstructionFactory
.GetNewObjectCreation(mappingData);
if (IsConstructableUsing(mappingData))
{
return;
}

if (objectCreation != null)
if (IsConstructableFromToTargetDataSources(mappingData, typeof(TDerivedTarget)))
{
return;
}
Expand All @@ -57,19 +61,57 @@ private void ThrowIfUnconstructable<TDerivedTarget>()
ThrowUnableToCreate<TDerivedTarget>();
}

var configuredImplementationPairings = _configInfo
.MapperContext
var configuredImplementationPairings = MapperContext
.UserConfigurations
.DerivedTypes.GetImplementationTypePairsFor(
_configInfo.ToMapperData(),
_configInfo.MapperContext);
MapperContext);

if (configuredImplementationPairings.None())
{
ThrowUnableToCreate<TDerivedTarget>();
}
}

private bool IsConstructableUsing(IObjectMappingData mappingData)
=> MapperContext.ConstructionFactory.GetNewObjectCreation(mappingData) != null;

private bool IsConstructableFromToTargetDataSources(IObjectMappingData mappingData, Type derivedTargetType)
{
var toTargetDataSources = MapperContext
.UserConfigurations
.GetDataSourcesForToTarget(mappingData.MapperData);

if (toTargetDataSources.None())
{
return false;
}

var constructionCheckMethod = typeof(DerivedPairTargetTypeSpecifier<TSource, TDerivedSource, TTarget>)
.GetNonPublicInstanceMethod(nameof(IsConstructableFromDataSource));

foreach (var dataSource in toTargetDataSources)
{
var isConstructable = (bool)constructionCheckMethod
.MakeGenericMethod(dataSource.SourceMember.Type, derivedTargetType)
.Invoke(this, Enumerable<object>.EmptyArray);

if (isConstructable)
{
return true;
}
}

return false;
}

private bool IsConstructableFromDataSource<TDataSource, TDerivedTarget>()
{
var mappingData = _configInfo.ToMappingData<TDataSource, TDerivedTarget>();

return IsConstructableUsing(mappingData);
}

private static void ThrowUnableToCreate<TDerivedTarget>()
{
throw new MappingConfigurationException(
Expand Down
3 changes: 1 addition & 2 deletions AgileMapper/Configuration/ConfiguredLambdaInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
{
using System;
using System.Linq;
using Extensions;
using Extensions.Internal;
using Members;
using Members.Dictionaries;
Expand Down Expand Up @@ -243,7 +242,7 @@ public Expression GetBody(
(mapperData.TargetMember is DictionaryTargetMember dictionaryMember) &&
(dictionaryMember.HasCompatibleType(contextTypes[1])))
{
contextTypes = contextTypes.ToArray();
contextTypes = contextTypes.CopyToArray();
contextTypes[1] = mapperData.TargetType;
}

Expand Down
3 changes: 2 additions & 1 deletion AgileMapper/Configuration/MappingConfigInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ public MappingConfigInfo Set<T>(T value)

public IObjectMappingData ToMappingData<TSource, TTarget>()
{
if (_mappingData != null)
if ((_mappingData != null) &&
_mappingData.MappingTypes.Equals(MappingTypes<TSource, TTarget>.Fixed))
{
return _mappingData;
}
Expand Down
23 changes: 20 additions & 3 deletions AgileMapper/Configuration/UserConfigurationSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -358,10 +358,27 @@ public ConfiguredDataSourceFactory GetDataSourceFactoryFor(MappingConfigInfo con
public bool HasConfiguredRootDataSources { get; private set; }

public IList<IConfiguredDataSource> GetDataSources(IMemberMapperData mapperData)
=> GetDataSources(QueryDataSourceFactories(mapperData), mapperData);

public IList<IConfiguredDataSource> GetDataSourcesForToTarget(IMemberMapperData mapperData)
{
if (!HasConfiguredRootDataSources)
{
return Enumerable<IConfiguredDataSource>.EmptyArray;
}

var toTargetDataSourceFactories =
QueryDataSourceFactories(mapperData)
.Filter(dsf => dsf.TargetMember.IsRoot);

return GetDataSources(toTargetDataSourceFactories, mapperData);
}

private static IList<IConfiguredDataSource> GetDataSources(
IEnumerable<ConfiguredDataSourceFactory> factories,
IMemberMapperData mapperData)
{
return (_dataSourceFactories != null)
? QueryDataSourceFactories(mapperData).Project(dsf => dsf.Create(mapperData)).ToArray()
: Enumerable<IConfiguredDataSource>.EmptyArray;
return factories.Project(dsf => dsf.Create(mapperData)).ToArray();
}

public IEnumerable<ConfiguredDataSourceFactory> QueryDataSourceFactories(IBasicMapperData mapperData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public IEnumerable<IDataSource> FindFor(DataSourceFindContext context)
.MapperData
.MapperContext
.UserConfigurations
.GetDataSources(updatedMapperData);
.GetDataSourcesForToTarget(updatedMapperData);

foreach (var configuredRootDataSource in configuredRootDataSources)
{
Expand Down
9 changes: 9 additions & 0 deletions AgileMapper/Extensions/Internal/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,15 @@ public static TResult[] ProjectToArray<TItem, TResult>(this IList<TItem> items,
return result;
}

public static T[] CopyToArray<T>(this IList<T> items)
{
var clonedArray = new T[items.Count];

clonedArray.CopyFrom(items);

return clonedArray;
}

public static Expression ReverseChain<T>(this IList<T> items)
where T : IConditionallyChainable
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public static TExpression Replace<TExpression>(
IEqualityComparer<Expression> comparer = null)
where TExpression : Expression
{
if (target == replacement)
{
return expression;
}

if (expression == null)
{
return null;
Expand Down
21 changes: 10 additions & 11 deletions AgileMapper/Extensions/PublicEnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,29 +150,28 @@ public static bool None<T>(this IEnumerable<T> items, Func<T, bool> predicate)
=> items.All(item => !predicate.Invoke(item));

/// <summary>
/// Convert this list of <paramref name="items"/> to an array.
/// Copies this list of <paramref name="items"/> into a new array.
/// </summary>
/// <typeparam name="T">The type of object stored in the list.</typeparam>
/// <param name="items">The list of items to convert.</param>
/// <returns>This list of items, converted to an array.</returns>
public static T[] ToArray<T>(this IList<T> items)
{
var array = new T[items.Count];

array.CopyFrom(items);

return array;
}
public static T[] ToArray<T>(this IList<T> items) => items.CopyToArray();

/// <summary>
/// Convert this collection of <paramref name="items"/> to an array.
/// Copies this collection of <paramref name="items"/> into a new array, or returns this
/// object if it is an array.
/// </summary>
/// <typeparam name="T">The type of object stored in the list.</typeparam>
/// <param name="items">The collection of items to convert.</param>
/// <returns>This collection of items, converted to an array.</returns>
public static T[] ToArray<T>(this ICollection<T> items)
{
var array = new T[items.Count];
if (items is T[] array)
{
return array;
}

array = new T[items.Count];

items.CopyTo(array, 0);

Expand Down
9 changes: 1 addition & 8 deletions AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,17 +172,10 @@ private IEnumerable<Expression> GetConfiguredRootDataSourcePopulations(MappingCr

protected static bool HasConfiguredRootDataSources(IMemberMapperData mapperData, out IList<IConfiguredDataSource> dataSources)
{
if (!mapperData.MapperContext.UserConfigurations.HasConfiguredRootDataSources)
{
dataSources = null;
return false;
}

dataSources = mapperData
.MapperContext
.UserConfigurations
.GetDataSources(mapperData)
.ToArray();
.GetDataSourcesForToTarget(mapperData);

return dataSources.Any();
}
Expand Down