-
Notifications
You must be signed in to change notification settings - Fork 210
/
PropertyTableAssigner.cs
260 lines (225 loc) · 9.51 KB
/
PropertyTableAssigner.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
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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MoonSharp.Interpreter.Compatibility;
namespace MoonSharp.Interpreter.Interop
{
/// <summary>
/// Utility class which may be used to set properties on an object of type T, from values contained in a Lua table.
/// Properties must be decorated with the <see cref="MoonSharpPropertyAttribute"/>.
/// This is a generic version of <see cref="PropertyTableAssigner"/>.
/// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
public class PropertyTableAssigner<T> : IPropertyTableAssigner
{
PropertyTableAssigner m_InternalAssigner;
/// <summary>
/// Initializes a new instance of the <see cref="PropertyTableAssigner{T}"/> class.
/// </summary>
/// <param name="expectedMissingProperties">The expected missing properties, that is expected fields in the table with no corresponding property in the object.</param>
public PropertyTableAssigner(params string[] expectedMissingProperties)
{
m_InternalAssigner = new PropertyTableAssigner(typeof(T), expectedMissingProperties);
}
/// <summary>
/// Adds an expected missing property, that is an expected field in the table with no corresponding property in the object.
/// </summary>
/// <param name="name">The name.</param>
public void AddExpectedMissingProperty(string name)
{
m_InternalAssigner.AddExpectedMissingProperty(name);
}
/// <summary>
/// Assigns properties from tables to an object.
/// </summary>
/// <param name="obj">The object.</param>
/// <param name="data">The table.</param>
/// <exception cref="System.ArgumentNullException">Object is null</exception>
/// <exception cref="ScriptRuntimeException">A field does not correspond to any property and that property is not one of the expected missing ones.</exception>
public void AssignObject(T obj, Table data)
{
m_InternalAssigner.AssignObject(obj, data);
}
/// <summary>
/// Gets the type-unsafe assigner corresponding to this object.
/// </summary>
/// <returns></returns>
public PropertyTableAssigner GetTypeUnsafeAssigner()
{
return m_InternalAssigner;
}
/// <summary>
/// Sets the subassigner for the given type. Pass null to remove usage of subassigner for the given type.
/// </summary>
/// <param name="propertyType">Type of the property for which the subassigner will be used.</param>
/// <param name="assigner">The property assigner.</param>
public void SetSubassignerForType(Type propertyType, IPropertyTableAssigner assigner)
{
m_InternalAssigner.SetSubassignerForType(propertyType, assigner);
}
/// <summary>
/// Sets the subassigner for the given type
/// </summary>
/// <typeparam name="SubassignerType">Type of the property for which the subassigner will be used.</typeparam>
/// <param name="assigner">The property assigner.</param>
public void SetSubassigner<SubassignerType>(PropertyTableAssigner<SubassignerType> assigner)
{
m_InternalAssigner.SetSubassignerForType(typeof(SubassignerType), assigner);
}
/// <summary>
/// Assigns the properties of the specified object without checking the type.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="data">The data.</param>
void IPropertyTableAssigner.AssignObjectUnchecked(object o, Table data)
{
AssignObject((T)o, data);
}
}
/// <summary>
/// Utility class which may be used to set properties on an object from values contained in a Lua table.
/// Properties must be decorated with the <see cref="MoonSharpPropertyAttribute"/>.
/// See <see cref="PropertyTableAssigner{T}"/> for a generic compile time type-safe version.
/// </summary>
public class PropertyTableAssigner : IPropertyTableAssigner
{
Type m_Type;
Dictionary<string, PropertyInfo> m_PropertyMap = new Dictionary<string, PropertyInfo>();
Dictionary<Type, IPropertyTableAssigner> m_SubAssigners = new Dictionary<Type, IPropertyTableAssigner>();
/// <summary>
/// Initializes a new instance of the <see cref="PropertyTableAssigner"/> class.
/// </summary>
/// <param name="type">The type of the object.</param>
/// <param name="expectedMissingProperties">The expected missing properties, that is expected fields in the table with no corresponding property in the object.</param>
/// <exception cref="System.ArgumentException">
/// Type cannot be a value type.
/// </exception>
public PropertyTableAssigner(Type type, params string[] expectedMissingProperties)
{
m_Type = type;
if (Framework.Do.IsValueType(m_Type))
throw new ArgumentException("Type cannot be a value type.");
foreach(string property in expectedMissingProperties)
{
m_PropertyMap.Add(property, null);
}
foreach (PropertyInfo pi in Framework.Do.GetProperties(m_Type))
{
foreach (MoonSharpPropertyAttribute attr in pi.GetCustomAttributes(true).OfType<MoonSharpPropertyAttribute>())
{
string name = attr.Name ?? pi.Name;
if (m_PropertyMap.ContainsKey(name))
{
throw new ArgumentException(string.Format("Type {0} has two definitions for MoonSharp property {1}", m_Type.FullName, name));
}
else
{
m_PropertyMap.Add(name, pi);
}
}
}
}
/// <summary>
/// Adds an expected missing property, that is an expected field in the table with no corresponding property in the object.
/// </summary>
/// <param name="name">The name.</param>
public void AddExpectedMissingProperty(string name)
{
m_PropertyMap.Add(name, null);
}
private bool TryAssignProperty(object obj, string name, DynValue value)
{
if (m_PropertyMap.ContainsKey(name))
{
PropertyInfo pi = m_PropertyMap[name];
if (pi != null)
{
object o;
if (value.Type == DataType.Table && m_SubAssigners.ContainsKey(pi.PropertyType))
{
var subassigner = m_SubAssigners[pi.PropertyType];
o = Activator.CreateInstance(pi.PropertyType);
subassigner.AssignObjectUnchecked(o, value.Table);
}
else
{
o = Interop.Converters.ScriptToClrConversions.DynValueToObjectOfType(value,
pi.PropertyType, null, false);
}
Framework.Do.GetSetMethod(pi).Invoke(obj, new object[] { o });
}
return true;
}
return false;
}
private void AssignProperty(object obj, string name, DynValue value)
{
if (TryAssignProperty(obj, name, value)) return;
if ((Script.GlobalOptions.FuzzySymbolMatching & FuzzySymbolMatchingBehavior.UpperFirstLetter) == FuzzySymbolMatchingBehavior.UpperFirstLetter && TryAssignProperty(obj, DescriptorHelpers.UpperFirstLetter(name), value)) return;
if ((Script.GlobalOptions.FuzzySymbolMatching & FuzzySymbolMatchingBehavior.Camelify) == FuzzySymbolMatchingBehavior.Camelify && TryAssignProperty(obj, DescriptorHelpers.Camelify(name), value)) return;
if ((Script.GlobalOptions.FuzzySymbolMatching & FuzzySymbolMatchingBehavior.PascalCase) == FuzzySymbolMatchingBehavior.PascalCase && TryAssignProperty(obj, DescriptorHelpers.UpperFirstLetter(DescriptorHelpers.Camelify(name)), value)) return;
throw new ScriptRuntimeException("Invalid property {0}", name);
}
/// <summary>
/// Assigns properties from tables to an object.
/// </summary>
/// <param name="obj">The object.</param>
/// <param name="data">The table.</param>
/// <exception cref="System.ArgumentNullException">Object is null</exception>
/// <exception cref="System.ArgumentException">The object is of an incompatible type.</exception>
/// <exception cref="ScriptRuntimeException">A field does not correspond to any property and that property is not one of the expected missing ones.</exception>
public void AssignObject(object obj, Table data)
{
if (obj == null)
throw new ArgumentNullException("Object is null");
if (!Framework.Do.IsInstanceOfType(m_Type, obj))
throw new ArgumentException(string.Format("Invalid type of object : got '{0}', expected {1}", obj.GetType().FullName, m_Type.FullName));
foreach (var pair in data.Pairs)
{
if (pair.Key.Type != DataType.String)
{
throw new ScriptRuntimeException("Invalid property of type {0}", pair.Key.Type.ToErrorTypeString());
}
AssignProperty(obj, pair.Key.String, pair.Value);
}
}
/// <summary>
/// Sets the subassigner for the given type. Pass null to remove usage of subassigner for the given type.
/// </summary>
/// <param name="propertyType">Type of the property for which the subassigner will be used.</param>
/// <param name="assigner">The property assigner.</param>
public void SetSubassignerForType(Type propertyType, IPropertyTableAssigner assigner)
{
if ( Framework.Do.IsAbstract(propertyType)
|| Framework.Do.IsGenericType(propertyType)
|| Framework.Do.IsInterface(propertyType)
|| Framework.Do.IsValueType(propertyType))
{
throw new ArgumentException("propertyType must be a concrete, reference type");
}
m_SubAssigners[propertyType] = assigner;
}
/// <summary>
/// Assigns the properties of the specified object without checking the type.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="data">The data.</param>
void IPropertyTableAssigner.AssignObjectUnchecked(object obj, Table data)
{
this.AssignObject(obj, data);
}
}
/// <summary>
/// Common interface for property assigners - basically used for sub-assigners
/// </summary>
public interface IPropertyTableAssigner
{
/// <summary>
/// Assigns the properties of the specified object without checking the type.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="data">The data.</param>
void AssignObjectUnchecked(object o, Table data);
}
}