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

JaCraig / ObjectCartographer / 18523983911

15 Oct 2025 09:16AM UTC coverage: 83.921% (-1.1%) from 85.034%
18523983911

push

github

web-flow
Merge pull request #323 from JaCraig/dependabot/nuget/ObjectCartographer/dependencies-009e2d9ae6

fix: Bump the dependencies group with 1 update

449 of 594 branches covered (75.59%)

Branch coverage included in aggregate %.

908 of 1023 relevant lines covered (88.76%)

6542.71 hits per line

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

80.49
/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
        /// Initializes a new instance of the <see cref="DataMapper"/> class.
23
        /// </summary>
24
        /// <param name="expressionBuilder">The expression builder.</param>
25
        /// <param name="logger">The logger.</param>
26
        public DataMapper(ExpressionBuilderManager expressionBuilder, ILogger<DataMapper>? logger = null)
3✔
27
        {
28
            Logger = logger;
3✔
29
            ExpressionBuilder = expressionBuilder;
3✔
30
            Instance = this;
3✔
31
        }
3✔
32

33
        /// <summary>
34
        /// Initializes a new instance of the <see cref="DataMapper"/> class.
35
        /// </summary>
36
        public DataMapper()
×
37
        {
38
            Instance = this;
×
39
        }
×
40

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

46
        /// <summary>
47
        /// The copy generic
48
        /// </summary>
49
        private static readonly MethodInfo _CopyGeneric = Array.Find(typeof(DataMapper).GetMethods(), x => string.Equals(x.Name, nameof(DataMapper.Copy), StringComparison.OrdinalIgnoreCase) && x.GetGenericArguments().Length == 2);
6✔
50

51
        /// <summary>
52
        /// The instance lock object
53
        /// </summary>
54
        private static readonly object _InstanceLockObject = new();
1✔
55

56
        /// <summary>
57
        /// The internal lock
58
        /// </summary>
59
        private static readonly object _InternalLock = new();
1✔
60

61
        /// <summary>
62
        /// The map create lock object
63
        /// </summary>
64
        private static readonly object _MapCreateLockObject = new();
1✔
65

66
        /// <summary>
67
        /// The generic map method.
68
        /// </summary>
69
        private static readonly MethodInfo _MapGeneric = typeof(DataMapper).GetMethod(nameof(DataMapper.Map), []);
1✔
70

71
        /// <summary>
72
        /// The instance
73
        /// </summary>
74
        private static DataMapper? _Instance;
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);
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
                Logger?.LogDebug("Mapping {Source} => {Destination}", Source, Destination);
71!
242
                var NewMapping = new TypeMapping<TSource, TDestination>(Key, Logger, ExpressionBuilder);
71✔
243
                _ = Types.TryAdd(Key, NewMapping);
71✔
244
                return NewMapping;
71✔
245
            }
246
        }
71✔
247

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

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

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

302
            Expression[] ArgsExpressions = parameters
113✔
303
                .Select((info, index) => CreateArgumentExpression(ParameterExpression, info, index))
86✔
304
                .ToArray();
113✔
305

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

308
            return (Expression.Lambda(
113✔
309
                typeof(MethodWrapperDelegate),
113✔
310
                CallExpression,
113✔
311
                ParameterExpression).Compile() as MethodWrapperDelegate)!;
113✔
312
        }
313
    }
314
}
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