FieldReflector.cs
1 //
2 // Copyright (c) Microsoft. All rights reserved.
3 // Licensed under the MIT license.
4 //
5 // Microsoft Bot Framework: http://botframework.com
6 //
7 // Bot Builder SDK GitHub:
8 // https://github.com/Microsoft/BotBuilder
9 //
10 // Copyright (c) Microsoft Corporation
11 // All rights reserved.
12 //
13 // MIT License:
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 //
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 //
25 // THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 
34 using System;
35 using System.Collections.Generic;
36 using System.Linq;
37 using System.Reflection;
38 
39 namespace Microsoft.Bot.Builder.FormFlow.Advanced
40 {
41  #region Documentation
42  #endregion
46  public class FieldReflector<T> : Field<T>
47  where T : class
48  {
49  #region Documentation
50  #endregion
54  public FieldReflector(string name, bool ignoreAnnotations = false)
55  : base(name, FieldRole.Value)
56  {
57  _ignoreAnnotations = ignoreAnnotations;
58  AddField(typeof(T), _name.Split('.'), 0);
59  }
60 
61  #region IField
62 
63  #region IFieldState
64  public override object GetValue(T state)
65  {
66  object current = state;
67  Type ftype = null;
68  foreach (var step in _path)
69  {
70  ftype = StepType(step);
71  var field = step as FieldInfo;
72  if (field != null)
73  {
74  current = field.GetValue(current);
75  }
76  else
77  {
78  var prop = (PropertyInfo)step;
79  current = prop.GetValue(current);
80  }
81  if (current == null)
82  {
83  break;
84  }
85  }
86  // Convert value types to null if appropriate
87  return (ftype.IsEnum
88  ? ((int)current == 0 ? null : current)
89  : (ftype == typeof(DateTime) && ((DateTime)current) == DateTime.MinValue)
90  ? null
91  : current);
92  }
93 
94  public override void SetValue(T state, object value)
95  {
96  object current = state;
97  object lastClass = state;
98  var last = _path.Last();
99  foreach (var step in _path)
100  {
101  var field = step as FieldInfo;
102  var prop = step as PropertyInfo;
103  Type ftype = StepType(step);
104  if (step == last)
105  {
106  object newValue = value;
107  var utype = Nullable.GetUnderlyingType(ftype) ?? ftype;
108  if (ftype.IsIEnumerable())
109  {
110  if (value != null && ftype != typeof(string))
111  {
112  // Build list and coerce elements
113  var list = Activator.CreateInstance(ftype);
114  var addMethod = list.GetType().GetMethod("Add");
115  foreach (var elt in (System.Collections.IEnumerable)value)
116  {
117  addMethod.Invoke(list, new object[] { elt });
118  }
119  newValue = list;
120  }
121  }
122  else
123  {
124  if (value == null)
125  {
126  if (!ftype.IsNullable() && (ftype.IsEnum || ftype.IsIntegral() || ftype.IsDouble()))
127  {
128  // Null value for non-nullable numbers and enums is 0
129  newValue = 0;
130  }
131  }
132  else if (utype.IsIntegral())
133  {
134  newValue = Convert.ChangeType(value, utype);
135  }
136  else if (utype.IsDouble())
137  {
138  newValue = Convert.ChangeType(value, utype);
139  }
140  else if (utype == typeof(bool))
141  {
142  newValue = Convert.ChangeType(value, utype);
143  }
144  }
145  if (field != null)
146  {
147  field.SetValue(lastClass, newValue);
148  }
149  else
150  {
151  prop.SetValue(lastClass, newValue);
152  }
153  }
154  else
155  {
156  current = (field == null ? prop.GetValue(current) : field.GetValue(current));
157  if (current == null)
158  {
159  var obj = Activator.CreateInstance(ftype);
160  current = obj;
161  if (field != null)
162  {
163  field.SetValue(lastClass, current);
164  }
165  else
166  {
167  prop.SetValue(lastClass, current);
168  }
169  }
170  lastClass = current;
171  }
172  }
173  }
174 
175  public override bool IsUnknown(T state)
176  {
177  var unknown = false;
178  var value = GetValue(state);
179  if (value == null)
180  {
181  unknown = true;
182  }
183  else
184  {
185  var step = _path.Last();
186  var ftype = StepType(step);
187  if (ftype.IsValueType && ftype.IsEnum)
188  {
189  unknown = ((int)value == 0);
190  }
191  else if (ftype == typeof(DateTime))
192  {
193  unknown = ((DateTime)value) == default(DateTime);
194  }
195  else if (ftype.IsIEnumerable())
196  {
197  unknown = !((System.Collections.IEnumerable)value).GetEnumerator().MoveNext();
198  }
199  }
200  return unknown;
201  }
202 
203  public override void SetUnknown(T state)
204  {
205  var step = _path.Last();
206  var field = step as FieldInfo;
207  var prop = step as PropertyInfo;
208  var ftype = StepType(step);
209  if (ftype.IsEnum)
210  {
211  SetValue(state, 0);
212  }
213  else if (ftype == typeof(DateTime))
214  {
215  SetValue(state, default(DateTime));
216  }
217  else
218  {
219  SetValue(state, null);
220  }
221  }
222 
223  #endregion
224  #endregion
225 
226  #region Internals
227  protected Type StepType(object step)
228  {
229  var field = step as FieldInfo;
230  var prop = step as PropertyInfo;
231  return (step == null ? null : (field == null ? prop.PropertyType : field.FieldType));
232  }
233 
234  protected void AddField(Type type, string[] path, int ipath)
235  {
236  if (ipath < path.Length)
237  {
238  ProcessTemplates(type);
239  var step = path[ipath];
240  object field = type.GetField(step, BindingFlags.Public | BindingFlags.Instance);
241  Type ftype;
242  if (field == null)
243  {
244  var prop = type.GetProperty(step, BindingFlags.Public | BindingFlags.Instance);
245  if (prop == null)
246  {
247  throw new MissingFieldException($"{step} is not a field or property in your type.");
248  }
249  field = prop;
250  ftype = prop.PropertyType;
251  _path.Add(prop);
252  }
253  else
254  {
255  ftype = (field as FieldInfo).FieldType;
256  _path.Add(field);
257  }
258  if (ftype.IsNullable())
259  {
260  _isNullable = true;
261  _keepZero = true;
262  ftype = Nullable.GetUnderlyingType(ftype);
263  }
264  else if (ftype.IsEnum || ftype.IsClass)
265  {
266  _isNullable = true;
267  }
268  if (ftype.IsClass)
269  {
270  if (ftype == typeof(string))
271  {
272  _type = ftype;
273  ProcessFieldAttributes(field);
274  }
275  else if (ftype.IsIEnumerable())
276  {
277  var elt = ftype.GetGenericElementType();
278  _type = elt;
279  _allowsMultiple = true;
280  ProcessFieldAttributes(field);
281  if (elt.IsEnum)
282  {
283  ProcessEnumAttributes(elt);
284  }
285  }
286  else
287  {
288  AddField(ftype, path, ipath + 1);
289  }
290  }
291  else
292  {
293  if (ftype.IsEnum)
294  {
295  ProcessFieldAttributes(field);
296  ProcessEnumAttributes(ftype);
297  }
298  else if (ftype == typeof(bool))
299  {
300  ProcessFieldAttributes(field);
301  }
302  else if (ftype.IsIntegral())
303  {
304  long min = long.MinValue;
305  long max = long.MaxValue;
306  if (ftype == typeof(sbyte)) { min = sbyte.MinValue; max = sbyte.MaxValue; }
307  else if (ftype == typeof(byte)) { min = byte.MinValue; max = byte.MaxValue; }
308  else if (ftype == typeof(short)) { min = short.MinValue; max = short.MaxValue; }
309  else if (ftype == typeof(ushort)) { min = ushort.MinValue; max = ushort.MaxValue; }
310  else if (ftype == typeof(int)) { min = int.MinValue; max = int.MaxValue; }
311  else if (ftype == typeof(uint)) { min = uint.MinValue; max = uint.MaxValue; }
312  else if (ftype == typeof(long)) { min = long.MinValue; max = long.MaxValue; }
313  else if (ftype == typeof(ulong)) { min = long.MinValue; max = long.MaxValue; }
314  SetLimits(min, max, false);
315  ProcessFieldAttributes(field);
316  }
317  else if (ftype.IsDouble())
318  {
319  double min = long.MinValue;
320  double max = long.MaxValue;
321  if (ftype == typeof(float)) { min = float.MinValue; max = float.MaxValue; }
322  else if (ftype == typeof(double)) { min = double.MinValue; max = double.MaxValue; }
323  SetLimits(min, max, false);
324  ProcessFieldAttributes(field);
325  }
326  else if (ftype == typeof(DateTime))
327  {
328  ProcessFieldAttributes(field);
329  }
330  _type = ftype;
331  }
332  }
333  }
334 
335  protected void ProcessTemplates(Type type)
336  {
337  if (!_ignoreAnnotations)
338  {
339  foreach (var attribute in type.GetCustomAttributes(typeof(TemplateAttribute)))
340  {
341  AddTemplate((TemplateAttribute)attribute);
342  }
343  }
344  }
345 
346  protected void ProcessFieldAttributes(object step)
347  {
348  _optional = false;
349  if (!_ignoreAnnotations)
350  {
351  var field = step as FieldInfo;
352  var prop = step as PropertyInfo;
353  var name = (field == null ? prop.Name : field.Name);
354  var describe = (field == null ? prop.GetCustomAttribute<DescribeAttribute>() : field.GetCustomAttribute<DescribeAttribute>());
355  var terms = (field == null ? prop.GetCustomAttribute<TermsAttribute>() : field.GetCustomAttribute<TermsAttribute>());
356  var prompt = (field == null ? prop.GetCustomAttribute<PromptAttribute>() : field.GetCustomAttribute<PromptAttribute>());
357  var optional = (field == null ? prop.GetCustomAttribute<OptionalAttribute>() : field.GetCustomAttribute<OptionalAttribute>());
358  var numeric = (field == null ? prop.GetCustomAttribute<NumericAttribute>() : field.GetCustomAttribute<NumericAttribute>());
359  var pattern = (field == null ? prop.GetCustomAttribute<PatternAttribute>() : field.GetCustomAttribute<PatternAttribute>());
360  if (describe != null)
361  {
362  _description = describe;
363  }
364  else
365  {
366  _description = new DescribeAttribute(Language.CamelCase(name));
367  }
368 
369  if (terms != null)
370  {
371  _terms = terms;
372  }
373  else
374  {
375  _terms = new TermsAttribute(Language.GenerateTerms(Language.CamelCase(name), 3));
376  }
377 
378  if (prompt != null)
379  {
380  SetPrompt(prompt);
381  }
382 
383  if (numeric != null)
384  {
385  double oldMin, oldMax;
386  Limits(out oldMin, out oldMax);
387  SetLimits(numeric.Min, numeric.Max, numeric.Min != oldMin || numeric.Max != oldMax);
388  }
389 
390  if (pattern != null)
391  {
392  SetPattern(pattern.Pattern);
393  }
394 
395  _optional = (optional != null);
396 
397  foreach (var attribute in (field == null ? prop.GetCustomAttributes<TemplateAttribute>() : field.GetCustomAttributes<TemplateAttribute>()))
398  {
399  var template = (TemplateAttribute)attribute;
400  AddTemplate(template);
401  }
402  }
403  }
404 
405  protected void ProcessEnumAttributes(Type type)
406  {
407  foreach (var enumField in type.GetFields(BindingFlags.Static | BindingFlags.Public))
408  {
409  var enumValue = enumField.GetValue(null);
410  if (_keepZero || (int)enumValue > 0)
411  {
412  var describe = enumField.GetCustomAttribute<DescribeAttribute>();
413  var terms = enumField.GetCustomAttribute<TermsAttribute>();
414  if (describe != null && !_ignoreAnnotations)
415  {
416  if (describe.Description == null)
417  {
418  describe.Description = Language.CamelCase(enumValue.ToString());
419  }
420  _valueDescriptions.Add(enumValue, describe);
421  }
422  else
423  {
424  _valueDescriptions.Add(enumValue, new DescribeAttribute(Language.CamelCase(enumValue.ToString())));
425  }
426 
427  if (terms != null && !_ignoreAnnotations)
428  {
429  _valueTerms.Add(enumValue, terms);
430  }
431  else
432  {
433  _valueTerms.Add(enumValue, new TermsAttribute(Language.GenerateTerms(Language.CamelCase(enumValue.ToString()), 4)));
434  }
435  }
436  }
437  }
438 
440  protected bool _ignoreAnnotations;
441 
443  protected List<object> _path = new List<object>();
444  #endregion
445  }
446 }
Core namespace for FormFlow and associated infrastructure.
Definition: Attributes.cs:39
FieldRole
The role the field plays in a form.
Definition: IField.cs:129
Define a template for generating strings.
Definition: Attributes.cs:571
void AddField(Type type, string[] path, int ipath)
Define the prompt used when asking about a field.
Definition: Attributes.cs:294
static string[] GenerateTerms(string phrase, int maxLength)
Generate regular expressions to match word sequences in original string.
Definition: Language.cs:215
Provide limits on the possible values in a numeric field or property.
Definition: Attributes.cs:625
Attribute to override the default terms used to match a field, property or enum value to user input...
Definition: Attributes.cs:119
static string CamelCase(string original)
Break a string into words based on _ and case changes.
Definition: Language.cs:146
Namespace for FormFlow advanced building blocks.
Definition: Attributes.cs:672
override void SetUnknown(T state)
Set this field value in form state to unknown.
Field is used to get a value to set in the form state.
FieldReflector(string name, bool ignoreAnnotations=false)
Construct an IField<T> through reflection.
Base class with declarative implementation of IField.
Definition: Field.cs:67
bool _ignoreAnnotations
True to ignore annotations.
Provide a regular expression to validate a string field.
Definition: Attributes.cs:657
Define a field or property as optional.
Definition: Attributes.cs:608
override object GetValue(T state)
Get this field value from form state.
override void SetValue(T state, object value)
Set this field value in form state.
Fill in field information through reflection.
override bool IsUnknown(T state)
Test to see if the field value form state has a value.
Root namespace for the Microsoft Bot Builder SDK.
Attribute to override the default description of a field, property or enum value. ...
Definition: Attributes.cs:62