forked from ThatRendle/Simple.Data
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPropertySetterBuilder.cs
More file actions
395 lines (335 loc) · 20 KB
/
PropertySetterBuilder.cs
File metadata and controls
395 lines (335 loc) · 20 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
using System.ComponentModel;
namespace Simple.Data
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
class PropertySetterBuilder
{
private static readonly MethodInfo DictionaryContainsKeyMethod = typeof(IDictionary<string, object>).GetMethod("ContainsKey", new[] { typeof(string) });
private static readonly PropertyInfo DictionaryIndexerProperty = typeof(IDictionary<string, object>).GetProperty("Item");
private static readonly MethodInfo ToArrayDictionaryMethod = typeof(Enumerable).GetMethod("ToArray",
BindingFlags.Public |
BindingFlags.Static).MakeGenericMethod(typeof(IDictionary<string, object>));
private static readonly MethodInfo ToArrayObjectMethod = typeof(Enumerable).GetMethod("ToArray",
BindingFlags.Public |
BindingFlags.Static).MakeGenericMethod(typeof(object));
private static readonly PropertyInfo ArrayDictionaryLengthProperty =
typeof(IDictionary<string, object>[]).GetProperty("Length");
private static readonly PropertyInfo ArrayObjectLengthProperty =
typeof(object[]).GetProperty("Length");
private readonly ParameterExpression _param;
private readonly ParameterExpression _obj;
private readonly PropertyInfo _property;
private MemberExpression _nameProperty;
private IndexExpression _itemProperty;
private MethodCallExpression _containsKey;
private static readonly MethodInfo CreatorCreateMethod = typeof(ConcreteTypeCreator).GetMethod("Create");
public PropertySetterBuilder(ParameterExpression param, ParameterExpression obj, PropertyInfo property)
{
_param = param;
_obj = obj;
_property = property;
}
public ConditionalExpression CreatePropertySetter()
{
CreatePropertyExpressions();
if (PropertyIsPrimitive())
{
return Expression.IfThen(_containsKey, CreateTrySimpleAssign());
}
if (_property.PropertyType.IsArray)
{
return Expression.IfThen(_containsKey, CreateTrySimpleArrayAssign());
}
if (_property.PropertyType.IsGenericCollection())
{
var collectionCreator = BuildCollectionCreator();
if (collectionCreator != null)
{
return Expression.IfThen(_containsKey, collectionCreator);
}
}
var isDictionary = Expression.TypeIs(_itemProperty, typeof(IDictionary<string, object>));
var tryComplexAssign = Expression.TryCatch(CreateComplexAssign(),
CreateCatchBlock());
var ifThen = Expression.IfThen(_containsKey, // if (dict.ContainsKey(propertyName)) {
Expression.IfThenElse(isDictionary, tryComplexAssign, CreateTrySimpleAssign()));
return ifThen;
}
private Expression BuildArrayCreator()
{
if (!_property.CanWrite) return null;
var genericType = _property.PropertyType.GetGenericArguments().Single();
var creatorInstance = ConcreteTypeCreator.Get(genericType);
var collection = Expression.Variable(_property.PropertyType);
var createCollection = MakeCreateNewCollection(collection, genericType);
if (createCollection == null) return null;
var addMethod = _property.PropertyType.GetMethod("Add");
if (addMethod == null) return null;
return BuildCollectionCreatorExpression(genericType, creatorInstance, collection, createCollection, addMethod);
}
private Expression BuildCollectionCreator()
{
var genericType = _property.PropertyType.GetGenericArguments().Single();
var creatorInstance = ConcreteTypeCreator.Get(genericType);
var collection = Expression.Variable(_property.PropertyType);
BinaryExpression createCollection = null;
if (_property.CanWrite)
{
createCollection = MakeCreateNewCollection(collection, genericType);
}
else
{
createCollection = Expression.Assign(collection, _nameProperty);
}
var addMethod = _property.PropertyType.GetInterfaceMethod("Add");
if (createCollection != null && addMethod != null)
{
return BuildCollectionCreatorExpression(genericType, creatorInstance, collection, createCollection, addMethod);
}
return null;
}
private Expression BuildCollectionCreatorExpression(Type genericType, ConcreteTypeCreator creatorInstance, ParameterExpression collection, BinaryExpression createCollection, MethodInfo addMethod)
{
BlockExpression dictionaryBlock;
var isDictionaryCollection = BuildComplexTypeCollectionPopulator(collection, genericType, addMethod, createCollection, creatorInstance, out dictionaryBlock);
BlockExpression objectBlock;
var isObjectcollection = BuildSimpleTypeCollectionPopulator(collection, genericType, addMethod, createCollection, creatorInstance, out objectBlock);
return Expression.IfThenElse(isDictionaryCollection, dictionaryBlock,
Expression.IfThen(isObjectcollection, objectBlock));
}
private TypeBinaryExpression BuildComplexTypeCollectionPopulator(ParameterExpression collection, Type genericType,
MethodInfo addMethod, BinaryExpression createCollection,
ConcreteTypeCreator creatorInstance, out BlockExpression block)
{
var creator = Expression.Constant(creatorInstance);
var array = Expression.Variable(typeof (IDictionary<string, object>[]));
var i = Expression.Variable(typeof (int));
var current = Expression.Variable(typeof (IDictionary<string, object>));
var isDictionaryCollection = Expression.TypeIs(_itemProperty,
typeof (IEnumerable<IDictionary<string, object>>));
var toArray = Expression.Assign(array,
Expression.Call(ToArrayDictionaryMethod,
Expression.Convert(_itemProperty,
typeof (IEnumerable<IDictionary<string, object>>))));
var start = Expression.Assign(i, Expression.Constant(0));
var label = Expression.Label();
var loop = Expression.Loop(
Expression.IfThenElse(
Expression.LessThan(i, Expression.Property(array, ArrayDictionaryLengthProperty)),
Expression.Block(
Expression.Assign(current, Expression.ArrayIndex(array, i)),
Expression.Call(collection, addMethod,
Expression.Convert(Expression.Call(creator, CreatorCreateMethod, current), genericType)),
Expression.PreIncrementAssign(i)
),
Expression.Break(label)
),
label
);
block = Expression.Block(
new[] {array, i, collection, current},
createCollection,
toArray,
start,
loop,
_property.CanWrite ? (Expression) Expression.Assign(_nameProperty, collection) : Expression.Empty());
return isDictionaryCollection;
}
private TypeBinaryExpression BuildSimpleTypeCollectionPopulator(ParameterExpression collection, Type genericType,
MethodInfo addMethod, BinaryExpression createCollection,
ConcreteTypeCreator creatorInstance, out BlockExpression block)
{
var creator = Expression.Constant(creatorInstance);
var array = Expression.Variable(typeof(object[]));
var i = Expression.Variable(typeof(int));
var current = Expression.Variable(typeof(object));
var isObjectCollection = Expression.TypeIs(_itemProperty,
typeof(IEnumerable<object>));
var toArray = Expression.Assign(array,
Expression.Call(ToArrayObjectMethod,
Expression.Convert(_itemProperty,
typeof(IEnumerable<object>))));
var start = Expression.Assign(i, Expression.Constant(0));
var label = Expression.Label();
var loop = Expression.Loop(
Expression.IfThenElse(
Expression.LessThan(i, Expression.Property(array, ArrayObjectLengthProperty)),
Expression.Block(
Expression.Assign(current, Expression.ArrayIndex(array, i)),
Expression.IfThenElse(
Expression.TypeIs(current, typeof(IDictionary<string,object>)),
Expression.Call(collection, addMethod,
Expression.Convert(Expression.Call(creator, CreatorCreateMethod,
Expression.Convert(current, typeof(IDictionary<string,object>))),
genericType)),
Expression.Call(collection, addMethod,
Expression.Convert(current, genericType))),
Expression.PreIncrementAssign(i)
),
Expression.Break(label)
),
label
);
block = Expression.Block(
new[] { array, i, collection, current },
createCollection,
toArray,
start,
loop,
_property.CanWrite ? (Expression)Expression.Assign(_nameProperty, collection) : Expression.Empty());
return isObjectCollection;
}
private BinaryExpression MakeCreateNewCollection(ParameterExpression collection, Type genericType)
{
BinaryExpression createCollection;
if (_property.PropertyType.IsInterface)
{
createCollection = Expression.Assign(collection,
Expression.Call(
typeof (PropertySetterBuilder).GetMethod("CreateList",
BindingFlags.
NonPublic |
BindingFlags.
Static).
MakeGenericMethod(genericType)));
}
else
{
var defaultConstructor = _property.PropertyType.GetConstructor(Type.EmptyTypes);
if (defaultConstructor != null)
{
createCollection = Expression.Assign(collection, Expression.New(defaultConstructor));
}
else
{
createCollection = null;
}
}
return createCollection;
}
private bool PropertyIsPrimitive()
{
return _property.PropertyType.IsPrimitive || _property.PropertyType == typeof(string) ||
_property.PropertyType == typeof(DateTime) || _property.PropertyType == typeof(byte[]) ||
_property.PropertyType.IsEnum ||
(_property.PropertyType.IsGenericType && _property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>));
}
private void CreatePropertyExpressions()
{
var name = Expression.Constant(_property.Name, typeof(string));
_containsKey = Expression.Call(_param, DictionaryContainsKeyMethod, name);
_nameProperty = Expression.Property(_obj, _property);
_itemProperty = Expression.Property(_param, DictionaryIndexerProperty, name);
}
private CatchBlock CreateCatchBlock()
{
return Expression.Catch(typeof(Exception), Expression.Assign(_nameProperty,
Expression.Default(_property.PropertyType)));
}
private BinaryExpression CreateComplexAssign()
{
var creator = Expression.Constant(ConcreteTypeCreator.Get(_property.PropertyType));
var methodCallExpression = Expression.Call(creator, CreatorCreateMethod,
// ReSharper disable PossiblyMistakenUseOfParamsMethod
Expression.Convert(_itemProperty,
typeof(IDictionary<string, object>)));
// ReSharper restore PossiblyMistakenUseOfParamsMethod
var complexAssign = Expression.Assign(_nameProperty,
Expression.Convert(
methodCallExpression, _property.PropertyType));
return complexAssign;
}
private TryExpression CreateTrySimpleAssign()
{
MethodCallExpression callConvert;
if (_property.PropertyType.IsEnum)
{
var changeTypeMethod = typeof (PropertySetterBuilder).GetMethod("SafeConvert",
BindingFlags.Static | BindingFlags.NonPublic);
callConvert = Expression.Call(changeTypeMethod, _itemProperty,
Expression.Constant(_property.PropertyType.GetEnumUnderlyingType(), typeof(Type)));
}
else if (_property.PropertyType.IsGenericType && _property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
var changeTypeMethod = typeof (PropertySetterBuilder)
.GetMethod("SafeConvertNullable", BindingFlags.Static | BindingFlags.NonPublic)
.MakeGenericMethod(_property.PropertyType.GetGenericArguments().Single());
callConvert = Expression.Call(changeTypeMethod, _itemProperty);
}
else
{
var changeTypeMethod = typeof (PropertySetterBuilder).GetMethod("SafeConvert",
BindingFlags.Static | BindingFlags.NonPublic);
callConvert = Expression.Call(changeTypeMethod, _itemProperty,
Expression.Constant(_property.PropertyType, typeof(Type)));
}
var assign = Expression.Assign(_nameProperty, Expression.Convert(callConvert, _property.PropertyType));
if (_property.PropertyType.IsEnum)
{
return Expression.TryCatch( // try {
Expression.IfThenElse(Expression.TypeIs(_itemProperty, typeof (string)),
Expression.Assign(_nameProperty,
Expression.Convert(Expression.Call(typeof (Enum).GetMethod("Parse", new[] {typeof(Type), typeof(string), typeof(bool)}),
Expression.Constant(_property.PropertyType, typeof(Type)),
Expression.Call(_itemProperty, typeof(object).GetMethod("ToString")), Expression.Constant(true)), _property.PropertyType)),
assign), Expression.Catch(typeof(Exception), Expression.Empty()));
}
return Expression.TryCatch( // try {
assign,
CreateCatchBlock());
}
private TryExpression CreateTrySimpleArrayAssign()
{
var createArrayMethod = typeof (PropertySetterBuilder).GetMethod("CreateArray", BindingFlags.Static | BindingFlags.NonPublic)
.MakeGenericMethod(_property.PropertyType.GetElementType());
var callConvert = Expression.Call(createArrayMethod, _itemProperty);
var assign = Expression.Assign(_nameProperty, Expression.Convert(callConvert, _property.PropertyType));
return Expression.TryCatch( // try {
Expression.IfThenElse(Expression.TypeIs(_itemProperty, typeof (string)),
Expression.Assign(_nameProperty,
Expression.Convert(Expression.Call(typeof (Enum).GetMethod("Parse", new[] {typeof(Type), typeof(string), typeof(bool)}),
Expression.Constant(_property.PropertyType, typeof(Type)),
Expression.Call(_itemProperty, typeof(object).GetMethod("ToString")), Expression.Constant(true)), _property.PropertyType)),
assign), Expression.Catch(typeof(Exception), Expression.Empty()));
}
// ReSharper disable UnusedMember.Local
// Because they're used from runtime-generated code, you see.
internal static object SafeConvert(object source, Type targetType)
{
if (ReferenceEquals(source, null)) return null;
if (targetType.IsInstanceOfType(source)) return source;
if (source is string && targetType == typeof(Guid)) return TypeDescriptor.GetConverter(typeof(Guid)).ConvertFromInvariantString(source.ToString());
return Convert.ChangeType(source, targetType);
}
internal static T? SafeConvertNullable<T>(object source)
where T : struct
{
if (ReferenceEquals(source, null)) return default(T?);
return (T) source;
}
private static T[] CreateArray<T>(object source)
{
if (ReferenceEquals(source, null)) return null;
var enumerable = source as IEnumerable;
if (ReferenceEquals(enumerable, null)) return null;
try
{
return enumerable.Cast<T>().ToArray();
}
catch (InvalidCastException)
{
return null;
}
}
private static List<T> CreateList<T>()
{
return new List<T>();
}
// ReSharper restore UnusedMember.Local
}
}