• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JaCraig / ObjectCartographer / 24028019128

06 Apr 2026 10:15AM UTC coverage: 83.558% (-1.1%) from 84.663%
24028019128

push

github

web-flow
Merge pull request #409 from JaCraig/dependabot/nuget/ObjectCartographer.Tests/dependencies-6a51363bcd

chore: Bump the dependencies group with 1 update

456 of 608 branches covered (75.0%)

Branch coverage included in aggregate %.

906 of 1022 relevant lines covered (88.65%)

14612.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.4
/ObjectCartographer/DataMapper.cs
1
using Fast.Activator;
2
using Microsoft.Extensions.DependencyInjection;
3
using Microsoft.Extensions.Logging;
4
using ObjectCartographer.ExpressionBuilder;
5
using ObjectCartographer.ExtensionMethods;
6
using ObjectCartographer.Interfaces;
7
using ObjectCartographer.Internal;
8
using System;
9
using System.Collections.Generic;
10
using System.Linq;
11
using System.Linq.Expressions;
12
using System.Reflection;
13

14
namespace ObjectCartographer
15
{
16
    /// <summary>
17
    /// Data mapper
18
    /// </summary>
19
    public class DataMapper
20
    {
21
        /// <summary>
22
        /// The copy create lock object
23
        /// </summary>
24
        private static readonly object _CopyCreateLockObject = new();
1✔
25

26
        /// <summary>
27
        /// The copy generic
28
        /// </summary>
29
        private static readonly MethodInfo? _CopyGeneric = Array.Find(typeof(DataMapper).GetMethods(), static x => string.Equals(x.Name, nameof(Copy), StringComparison.OrdinalIgnoreCase) && x.GetGenericArguments().Length == 2);
6✔
30

31
        /// <summary>
32
        /// The instance lock object
33
        /// </summary>
34
        private static readonly object _InstanceLockObject = new();
1✔
35

36
        /// <summary>
37
        /// The internal lock
38
        /// </summary>
39
        private static readonly object _InternalLock = new();
1✔
40

41
        /// <summary>
42
        /// The map create lock object
43
        /// </summary>
44
        private static readonly object _MapCreateLockObject = new();
1✔
45

46
        /// <summary>
47
        /// The generic map method.
48
        /// </summary>
49
        private static readonly MethodInfo? _MapGeneric = typeof(DataMapper).GetMethod(nameof(DataMapper.Map), []);
1✔
50

51
        /// <summary>
52
        /// The instance
53
        /// </summary>
54
        private static DataMapper? _Instance;
55

56
        /// <summary>
57
        /// Initializes a new instance of the <see cref="DataMapper"/> class.
58
        /// </summary>
59
        /// <param name="expressionBuilder">The expression builder.</param>
60
        /// <param name="logger">The logger.</param>
61
        public DataMapper(ExpressionBuilderManager expressionBuilder, ILogger<DataMapper>? logger = null)
3✔
62
        {
63
            Logger = logger;
3✔
64
            ExpressionBuilder = expressionBuilder;
3✔
65
            Instance = this;
3✔
66
        }
3✔
67

68
        /// <summary>
69
        /// Initializes a new instance of the <see cref="DataMapper"/> class.
70
        /// </summary>
71
        public DataMapper()
×
72
        {
73
            Instance = this;
×
74
        }
×
75

76
        /// <summary>
77
        /// Gets the instance.
78
        /// </summary>
79
        /// <value>The instance.</value>
80
        internal static DataMapper? Instance
81
        {
82
            get
83
            {
84
                if (_Instance is not null)
211!
85
                    return _Instance;
211✔
86
                lock (_InstanceLockObject)
×
87
                {
88
                    if (_Instance is not null)
×
89
                        return _Instance;
×
90
                    _Instance = Services.ServiceProvider?.GetService<DataMapper>();
×
91
                }
×
92
                return _Instance;
×
93
            }
×
94
            set => _Instance = value;
3✔
95
        }
96

97
        /// <summary>
98
        /// Gets the create instance.
99
        /// </summary>
100
        /// <value>The create instance.</value>
101
        private static MethodInfo? CreateInstance { get; } = typeof(FastActivator).GetMethod(nameof(FastActivator.CreateInstance), 1, []);
13✔
102

103
        /// <summary>
104
        /// Gets the copy methods.
105
        /// </summary>
106
        /// <value>The copy methods.</value>
107
        private Dictionary<TypeTuple, MethodWrapperDelegate> CopyMethods { get; } = [];
184✔
108

109
        /// <summary>
110
        /// Gets the expression builder.
111
        /// </summary>
112
        /// <value>The expression builder.</value>
113
        private ExpressionBuilderManager? ExpressionBuilder { get; }
71✔
114

115
        /// <summary>
116
        /// Gets the logger.
117
        /// </summary>
118
        /// <value>The logger.</value>
119
        private ILogger<DataMapper>? Logger { get; }
142✔
120

121
        /// <summary>
122
        /// Gets the map methods.
123
        /// </summary>
124
        /// <value>The map methods.</value>
125
        private Dictionary<TypeTuple, MethodWrapperDelegate> MapMethods { get; } = [];
219✔
126

127
        /// <summary>
128
        /// Gets the types.
129
        /// </summary>
130
        /// <value>The types.</value>
131
        private Dictionary<TypeTuple, ITypeMapping> Types { get; } = [];
355✔
132

133
        /// <summary>
134
        /// Automatically maps the two types.
135
        /// </summary>
136
        /// <param name="first">The first.</param>
137
        /// <param name="second">The second.</param>
138
        /// <returns>This.</returns>
139
        public DataMapper AutoMap(Type first, Type second)
140
        {
141
            if (first is null || second is null)
39✔
142
                return this;
1✔
143
            Map(first, second)?.AutoMap().Build();
38!
144
            Map(second, first)?.AutoMap().Build();
38!
145
            return this;
38✔
146
        }
147

148
        /// <summary>
149
        /// Automatically maps the two types.
150
        /// </summary>
151
        /// <typeparam name="TFirst">The type of the first.</typeparam>
152
        /// <typeparam name="TSecond">The type of the second.</typeparam>
153
        /// <returns>This.</returns>
154
        public DataMapper AutoMap<TFirst, TSecond>() => AutoMap(typeof(TFirst), typeof(TSecond));
38✔
155

156
        /// <summary>
157
        /// Copies the specified source to the destination object (or a new TDestination object if
158
        /// one is not passed in).
159
        /// </summary>
160
        /// <typeparam name="TDestination">The type of the destination.</typeparam>
161
        /// <param name="source">The source.</param>
162
        /// <param name="destination">The destination.</param>
163
        /// <returns>The resulting object</returns>
164
        public TDestination Copy<TDestination>(object? source, TDestination destination = default!) => (TDestination)Copy(source, destination, typeof(TDestination))!;
13✔
165

166
        /// <summary>
167
        /// Copies the specified source to the destination
168
        /// </summary>
169
        /// <param name="source">The source.</param>
170
        /// <param name="destination">The destination.</param>
171
        /// <param name="destinationType">
172
        /// Type of the destination (if null, system uses the type of the destination object).
173
        /// </param>
174
        /// <returns>The resulting object.</returns>
175
        public object? Copy(object? source, object? destination, Type? destinationType = null)
176
        {
177
            if (source is null)
212✔
178
                return destination;
117✔
179
            Type Source = source.GetType();
95✔
180
            Type? Destination = destinationType ?? destination?.GetType();
95!
181
            if (Destination is null)
95!
182
                return destination;
×
183
            var Key = new TypeTuple(Source, Destination);
95✔
184
            if (CopyMethods.TryGetValue(Key, out MethodWrapperDelegate? Method))
95✔
185
                return Method([source, destination]);
52✔
186
            lock (_CopyCreateLockObject)
43✔
187
            {
188
                if (CopyMethods.TryGetValue(Key, out Method))
43!
189
                    return Method([source, destination]);
×
190
                MethodInfo GenericMethod = _CopyGeneric?.MakeGenericMethod(Source, Destination) ?? throw new InvalidOperationException("Failed to create generic method.");
43!
191
                MethodWrapperDelegate FinalMethod = CreateMethod(GenericMethod, GenericMethod.GetParameters());
43✔
192
                _ = CopyMethods.TryAdd(Key, FinalMethod);
43✔
193
                return FinalMethod([source, destination]);
43✔
194
            }
195
        }
43✔
196

197
        /// <summary>
198
        /// Copies the specified source to the destination object (or a new TDestination object if
199
        /// one is not passed in).
200
        /// </summary>
201
        /// <typeparam name="TSource">The type of the source.</typeparam>
202
        /// <typeparam name="TDestination">The type of the destination.</typeparam>
203
        /// <param name="source">The source.</param>
204
        /// <param name="destination">The destination.</param>
205
        /// <returns>The resulting object</returns>
206
        public TDestination Copy<TSource, TDestination>(TSource source, TDestination destination = default!)
207
        {
208
            if (source is null)
96✔
209
                return destination;
1✔
210
            Type Source = source.GetType();
95✔
211
            Type Destination = typeof(TDestination);
95✔
212
            var Key = new TypeTuple(Source, Destination);
95✔
213
            if (!Types.TryGetValue(Key, out ITypeMapping? ReturnValue))
95✔
214
            {
215
                _ = AutoMap<TSource, TDestination>();
36✔
216
                if (!Types.TryGetValue(Key, out ReturnValue))
36!
217
                    return destination;
×
218
            }
219
            var Mapping = ReturnValue as TypeMapping<TSource, TDestination>;
95✔
220
            Mapping?.Build();
95!
221
            return Mapping.Converter(source, destination);
95✔
222
        }
223

224
        /// <summary>
225
        /// Maps the source type to the destination type
226
        /// </summary>
227
        /// <typeparam name="TSource">The type of the source.</typeparam>
228
        /// <typeparam name="TDestination">The type of the destination.</typeparam>
229
        /// <returns>The type mapping.</returns>
230
        public TypeMapping<TSource, TDestination>? Map<TSource, TDestination>()
231
        {
232
            Type Source = typeof(TSource);
79✔
233
            Type Destination = typeof(TDestination);
79✔
234
            var Key = new TypeTuple(Source, Destination);
79✔
235
            if (Types.TryGetValue(Key, out ITypeMapping? ReturnValue))
79✔
236
                return ReturnValue as TypeMapping<TSource, TDestination>;
8✔
237
            lock (_InternalLock)
71✔
238
            {
239
                if (Types.TryGetValue(Key, out ReturnValue))
71!
240
                    return ReturnValue as TypeMapping<TSource, TDestination>;
×
241
                if (Logger?.IsEnabled(LogLevel.Debug) == true)
71!
242
                {
243
                    Logger.LogDebug("Mapping {Source} => {Destination}", Source, Destination);
×
244
                }
245
                var NewMapping = new TypeMapping<TSource, TDestination>(Key, Logger, ExpressionBuilder);
71✔
246
                _ = Types.TryAdd(Key, NewMapping);
71✔
247
                return NewMapping;
71✔
248
            }
249
        }
71✔
250

251
        /// <summary>
252
        /// Maps the specified source to the destination.
253
        /// </summary>
254
        /// <param name="source">The source.</param>
255
        /// <param name="destination">The destination.</param>
256
        /// <returns>The type mapping.</returns>
257
        public ITypeMapping? Map(Type source, Type destination)
258
        {
259
            if (source is null || destination is null)
77✔
260
                return null;
1✔
261
            var Key = new TypeTuple(source, destination);
76✔
262
            if (MapMethods.TryGetValue(Key, out MethodWrapperDelegate? Method))
76✔
263
                return Method([]) as ITypeMapping;
6✔
264
            lock (_MapCreateLockObject)
70✔
265
            {
266
                if (MapMethods.TryGetValue(Key, out Method))
70!
267
                    return Method([]) as ITypeMapping;
×
268
                MethodInfo GenericMethod = _MapGeneric?.MakeGenericMethod(source, destination) ?? throw new InvalidOperationException("Failed to create generic method.");
70!
269
                MethodWrapperDelegate FinalMethod = CreateMethod(GenericMethod, GenericMethod.GetParameters());
70✔
270
                _ = MapMethods.TryAdd(Key, FinalMethod);
70✔
271
                return FinalMethod([]) as ITypeMapping;
70✔
272
            }
273
        }
70✔
274

275
        /// <summary>
276
        /// Creates the argument expression.
277
        /// </summary>
278
        /// <param name="parameterExpression">The parameter expression.</param>
279
        /// <param name="parameterInfo">The parameter information.</param>
280
        /// <param name="index">The index.</param>
281
        /// <returns>The argument expression</returns>
282
        private static UnaryExpression CreateArgumentExpression(ParameterExpression parameterExpression, ParameterInfo parameterInfo, int index)
283
        {
284
            BinaryExpression IndexValue = Expression.ArrayIndex(parameterExpression, Expression.Constant(index));
86✔
285
            if (DefaultValueLookup.Values.TryGetValue(parameterInfo.ParameterType.GetHashCode(), out var DefaultValue))
86✔
286
                IndexValue = Expression.Coalesce(IndexValue, Expression.Constant(DefaultValue));
30✔
287
            else if (!parameterInfo.ParameterType.IsClass)
56✔
288
                IndexValue = Expression.Coalesce(IndexValue, Expression.Call(CreateInstance?.MakeGenericMethod(parameterInfo.ParameterType) ?? throw new InvalidOperationException("Failed to create generic method.")));
12!
289
            return Expression.Convert(
86✔
290
                IndexValue,
86✔
291
                parameterInfo.ParameterType);
86✔
292
        }
293

294
        /// <summary>
295
        /// Creates the method wrapper.
296
        /// </summary>
297
        /// <param name="method">The method.</param>
298
        /// <param name="parameters">The parameters.</param>
299
        /// <returns>The resulting method.</returns>
300
        private MethodWrapperDelegate CreateMethod(MethodInfo method, ParameterInfo[] parameters)
301
        {
302
            ParameterExpression ParameterExpression = Expression.Parameter(typeof(object[]), "args");
113✔
303
            ConstantExpression ThisValue = Expression.Constant(this);
113✔
304

305
            Expression[] ArgsExpressions = [.. parameters.Select((info, index) => CreateArgumentExpression(ParameterExpression, info, index))];
199✔
306

307
            Expression CallExpression = Expression.Convert(Expression.Call(ThisValue, method, ArgsExpressions), typeof(object));
113✔
308

309
            return (Expression.Lambda(
113✔
310
                typeof(MethodWrapperDelegate),
113✔
311
                CallExpression,
113✔
312
                ParameterExpression).Compile() as MethodWrapperDelegate)!;
113✔
313
        }
314
    }
315
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc