forked from petabridge/faker-csharp
/
Matcher.cs
224 lines (190 loc) · 8.76 KB
/
Matcher.cs
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
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Faker.Generators;
using Faker.Helpers;
using Faker.Selectors;
namespace Faker
{
/// <summary>
/// Class used to match a type selector from the TypeTable with the properties of an object
/// </summary>
public class Matcher
{
public TypeTable TypeMap { get; protected set; }
/// <summary>
/// Default constructor - uses the default TypeTable
/// </summary>
public Matcher() : this(new TypeTable()) { }
/// <summary>
/// Constructor which accepts a TypeTable as an argument
/// </summary>
/// <param name="table">an instantiated type table that can be accessed via the TypeMap property later</param>
public Matcher(TypeTable table)
{
TypeMap = table;
}
/// <summary>
/// Method matches all properties on a given class with a
/// </summary>
/// <typeparam name="T">a class with a parameterless constructor (POCO class)</typeparam>
/// <param name="targetObject">an instance of the class</param>
public virtual void Match<T>(T targetObject) where T : new()
{
//Get all of the properties of the class
var properties = typeof(T).GetProperties();
ProcessProperties(properties, targetObject);
}
/// <summary>
/// Method for iterating over all of the indivdual properties in for a given object
/// </summary>
/// <param name="properties">The set of properties available to an object instance</param>
/// <param name="targetObject">The object against which type selectors will inject values</param>
protected virtual void ProcessProperties(PropertyInfo[] properties, object targetObject)
{
//Iterate over the properties
foreach (var property in properties)
{
if (!property.CanWrite) //Bail if we can't write to the property
continue;
ProcessProperty(property, targetObject);
}
}
/// <summary>
/// Protected method used to implement our selector-matching strategy. Uses a greedy approach.
/// </summary>
/// <param name="property">The meta-data about the property for which we will be finding a match</param>
/// <param name="targetObject">The object which will receive the property injection</param>
protected virtual void ProcessProperty(PropertyInfo property, object targetObject)
{
//Get the type of the property
var propertyType = property.PropertyType;
//Determine if we have a selector-on-hand for this data type
var selectorCount = TypeMap.CountSelectors(propertyType);
//We have some matching selectors, so we'll evaluate and return the best match
if (selectorCount > 0)
{
//Evaluate all of the possible selectors and find the first available match
var selector = EvaluateSelectors(property, TypeMap.GetSelectors(propertyType));
//We found a matching selector
if (!(selector is MissingSelector))
{
selector.Generate(targetObject, property); //Bind the property
return; //Exit
}
}
//Check to see if the type is a class and has a default constructor
if (propertyType.IsClass && propertyType.GetConstructor(Type.EmptyTypes) != null && !IsArray(propertyType))
{
var subProperties = propertyType.GetProperties();
//Create an instance of the underlying subclass
var subClassInstance = Activator.CreateInstance(propertyType);
//Match all of the properties on the subclass
ProcessProperties(subProperties, subClassInstance);
//Bind the sub-class back onto the original target object
property.SetValue(targetObject, subClassInstance, null);
return; //Exit
}
//Check to see if the type is an array or any other sort of collection
if (IsArray(propertyType))
{
//Get the underlying type used int he array
//var elementType = propertyType.GetElementType(); //Works only for arrays
var elementType = propertyType.GetGenericArguments()[0]; //Works for IList<T> / IEnumerable<T>
//Get a number of elements we want to create
//Note: (between 1 and 10 for now)
var elementCount = Numbers.Int(1, 10);
//Create an instance of our target array
IList arrayInstance = null;
//If we're working with a generic list or any other sort of collection
if (propertyType.IsGenericTypeDefinition)
{
arrayInstance = (IList)GenericHelper.CreateGeneric(propertyType, elementType);
}
else
{
arrayInstance = (IList)GenericHelper.CreateGeneric(typeof(List<>), elementType);
}
//Determine if there's a selector available for this type
var hasSelector = TypeMap.CountSelectors(elementType) > 0;
ITypeSelector selector = null;
//So we have a type available for this selector..
if (hasSelector)
{
selector = TypeMap.GetBaseSelector(elementType);
}
for (var i = 0; i < elementCount; i++)
{
//Create a new element instance
var element = SafeObjectCreate(elementType);
if (hasSelector)
{
selector.Generate(ref element);
}
else if (elementType.IsClass) //If the element type is a sub-class, then populate it recursively
{
var subProperties = elementType.GetProperties();
//Populate all of the properties on this object
ProcessProperties(subProperties, element);
}
arrayInstance.Add(element);
}
//Bind the sub-class back onto the original target object
property.SetValue(targetObject, arrayInstance, null);
}
}
/// <summary>
/// Returns true if the targeted type is an array of some sort
/// </summary>
/// <param name="targetType">the type we want to test</param>
/// <returns>true if it's an array, false otherwise</returns>
protected virtual bool IsArray(Type targetType)
{
if (!targetType.IsGenericType)
return false;
var genericArguments = targetType.GetGenericArguments();
if (genericArguments.Length != 1)
return false;
var listType = typeof(IList<>).MakeGenericType(genericArguments);
return listType.IsAssignableFrom(targetType);
}
/// <summary>
/// Method used for safely creating new instances of type objects; handles a few special cases
/// where activation has to be done carefully.
/// </summary>
/// <param name="t">The target type we want to instantiate</param>
/// <returns>an instance of the specified type</returns>
public static object SafeObjectCreate(Type t)
{
//If the object is a string (tricky)
if (t == typeof(string))
{
return string.Empty;
}
return Activator.CreateInstance(t);
}
/// <summary>
/// Evaluates a set of selectors and grabs the first available match
/// </summary>
/// <param name="propertyType">The type for which we're trying to find a match</param>
/// <param name="selectors">A list of selectors from the TypeTable</param>
/// <returns>the first matching ITypeSelector instance we could find</returns>
protected virtual ITypeSelector EvaluateSelectors(PropertyInfo propertyType, IEnumerable<ITypeSelector> selectors)
{
foreach (var selector in selectors)
{
//If the selector can bind
if (selector.CanBind(propertyType))
{
//Return it
return selector;
}
}
//Otherwise, return a MissingSelector and let them know that we can't do it.
return new MissingSelector();
}
}
}