Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
MemberPath.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections;
5 using System.Collections.Generic;
6 using System.Text;
7 
8 namespace SiliconStudio.Core.Reflection
9 {
10  /// <summary>
11  /// Allows to get/set a property/field value on a deeply nested object instance (supporting
12  /// members, list access and dictionary access)
13  /// </summary>
14  public sealed class MemberPath
15  {
16  /// <summary>
17  /// We use a thread local static to avoid allocating a list of reference objects every time we access a property
18  /// </summary>
19  [ThreadStatic] private static List<object> stackTLS;
20 
21  private readonly List<MemberPathItem> items;
22 
23  /// <summary>
24  /// Initializes a new instance of the <see cref="MemberPath"/> class.
25  /// </summary>
26  public MemberPath() : this(16)
27  {
28  }
29 
30  /// <summary>
31  /// Initializes a new instance of the <see cref="MemberPath"/> class.
32  /// </summary>
33  /// <param name="capacity">The capacity.</param>
34  public MemberPath(int capacity)
35  {
36  items = new List<MemberPathItem>(capacity);
37  }
38 
39  /// <summary>
40  /// Initializes a new instance of the <see cref="MemberPath"/> class.
41  /// </summary>
42  /// <param name="items">The items.</param>
43  private MemberPath(List<MemberPathItem> items)
44  {
45  if (items == null) throw new ArgumentNullException("items");
46  this.items = new List<MemberPathItem>(items.Capacity);
47  this.items.AddRange(items);
48  }
49 
50  /// <summary>
51  /// Ensures the capacity of the paths definition when using <see cref="Push(SiliconStudio.Core.Reflection.IMemberDescriptor)"/> methods.
52  /// </summary>
53  /// <param name="pathCount">The path count.</param>
54  public void EnsureCapacity(int pathCount)
55  {
56  items.Capacity = pathCount;
57  }
58 
59  /// <summary>
60  /// Clears the current path.
61  /// </summary>
62  public void Clear()
63  {
64  items.Clear();
65  }
66 
67  /// <summary>
68  /// Gets the custom attribute of the last property/field from this member path.
69  /// </summary>
70  /// <typeparam name="T">Type of the attribute</typeparam>
71  /// <returns>A custom attribute or null if not found</returns>
72  public T GetCustomAttribute<T>() where T : Attribute
73  {
74  if (items == null || items.Count == 0)
75  {
76  return null;
77  }
78 
79  for(int i = items.Count - 1; i >= 0; i--)
80  {
81  var descriptor = items[i].MemberDescriptor as MemberDescriptorBase;
82  if (descriptor == null)
83  {
84  continue;
85  }
86  var attributes = descriptor.MemberInfo.GetCustomAttributes(typeof(T), false);
87  if (attributes.Length > 0)
88  {
89  return (T)attributes[0];
90  }
91 
92  break;
93  }
94  return null;
95  }
96 
97  /// <summary>
98  /// Pushes a member access on the path.
99  /// </summary>
100  /// <param name="descriptor">The descriptor of the member.</param>
101  /// <exception cref="System.ArgumentNullException">descriptor</exception>
102  public void Push(IMemberDescriptor descriptor)
103  {
104  if (descriptor == null) throw new ArgumentNullException("descriptor");
105  AddItem(descriptor is FieldDescriptor ? (MemberPathItem)new FieldPathItem((FieldDescriptor)descriptor) : new PropertyPathItem((PropertyDescriptor)descriptor));
106  }
107 
108  /// <summary>
109  /// Pushes an array access on the path.
110  /// </summary>
111  /// <param name="descriptor">The descriptor of the array.</param>
112  /// <param name="index">The index in the array.</param>
113  /// <exception cref="System.ArgumentNullException">descriptor</exception>
114  public void Push(ArrayDescriptor descriptor, int index)
115  {
116  if (descriptor == null) throw new ArgumentNullException("descriptor");
117  AddItem(new ArrayPathItem(index));
118  }
119 
120  /// <summary>
121  /// Pushes an collection access on the path.
122  /// </summary>
123  /// <param name="descriptor">The descriptor of the collection.</param>
124  /// <param name="index">The index in the collection.</param>
125  /// <exception cref="System.ArgumentNullException">descriptor</exception>
126  public void Push(CollectionDescriptor descriptor, int index)
127  {
128  if (descriptor == null) throw new ArgumentNullException("descriptor");
129  AddItem(new CollectionPathItem(descriptor, index));
130  }
131 
132  /// <summary>
133  /// Pushes an dictionary access on the path.
134  /// </summary>
135  /// <param name="descriptor">The descriptor of the dictionary.</param>
136  /// <param name="key">The key.</param>
137  /// <exception cref="System.ArgumentNullException">descriptor</exception>
138  public void Push(DictionaryDescriptor descriptor, object key)
139  {
140  if (descriptor == null) throw new ArgumentNullException("descriptor");
141  AddItem(new DictionaryPathItem(descriptor, key));
142  }
143 
144  /// <summary>
145  /// Pops the last item from the current path.
146  /// </summary>
147  public void Pop()
148  {
149  if (items.Count > 0)
150  {
151  items.RemoveAt(items.Count - 1);
152  }
153  }
154 
155  public bool Apply(object rootObject, MemberPathAction actionType, object value)
156  {
157  if (rootObject == null) throw new ArgumentNullException("rootObject");
158  if (rootObject.GetType().IsValueType) throw new ArgumentException("Value type for root objects are not supported", "rootObject");
159  if (actionType != MemberPathAction.ValueSet && actionType != MemberPathAction.CollectionAdd && value != null)
160  {
161  throw new ArgumentException("Value must be null for action != (MemberActionType.SetValue || MemberPathAction.CollectionAdd)");
162  }
163 
164  if (items == null || items.Count == 0)
165  {
166  throw new InvalidOperationException("This instance doesn't contain any path. Use Push() methods to populate paths");
167  }
168 
169  var lastItem = items[items.Count - 1];
170  switch (actionType)
171  {
172  case MemberPathAction.CollectionAdd:
173  if (!(lastItem is CollectionPathItem))
174  {
175  throw new ArgumentException("Invalid path [{0}] for action [{1}]. Expecting last path to be a collection item".ToFormat(this, actionType));
176  }
177  break;
178  case MemberPathAction.CollectionRemove:
179  if (!(lastItem is CollectionPathItem) && !(lastItem is ArrayPathItem))
180  {
181  throw new ArgumentException("Invalid path [{0}] for action [{1}]. Expecting last path to be a collection/array item".ToFormat(this, actionType));
182  }
183  break;
184 
185  case MemberPathAction.DictionaryRemove:
186  if (!(lastItem is DictionaryPathItem))
187  {
188  throw new ArgumentException("Invalid path [{0}] for action [{1}]. Expecting last path to be a dictionary item".ToFormat(this, actionType));
189  }
190  break;
191  }
192 
193  var stack = stackTLS;
194  try
195  {
196  object nextObject = rootObject;
197 
198  if (stack == null)
199  {
200  stack = new List<object>();
201  stackTLS = stack;
202  }
203  else
204  {
205  stack.Clear();
206  }
207 
208  stack.Add(nextObject);
209  for (int i = 0; i < items.Count - 1; i++)
210  {
211  var item = items[i];
212  nextObject = item.GetValue(nextObject);
213  stack.Add(nextObject);
214  }
215 
216  switch (actionType)
217  {
218  case MemberPathAction.ValueSet:
219  lastItem.SetValue(stack, stack.Count - 1, nextObject, value);
220  break;
221 
222  case MemberPathAction.DictionaryRemove:
223  ((DictionaryPathItem)lastItem).Descriptor.Remove(nextObject, ((DictionaryPathItem)lastItem).Key);
224  break;
225 
226  case MemberPathAction.CollectionAdd:
227  ((CollectionPathItem)lastItem).Descriptor.Add(nextObject, value);
228  break;
229 
230  case MemberPathAction.CollectionRemove:
231  ((CollectionPathItem)lastItem).Descriptor.RemoveAt(nextObject, ((CollectionPathItem)lastItem).Index);
232  break;
233  }
234  }
235  catch (Exception)
236  {
237  // If an exception occured, we cannot resolve this member path to a valid property/field
238  return false;
239  }
240  finally
241  {
242  if (stack != null)
243  {
244  stack.Clear();
245  }
246  }
247  return true;
248  }
249 
250  /// <summary>
251  /// Gets the value from the specified root object following this instance path.
252  /// </summary>
253  /// <param name="rootObject">The root object.</param>
254  /// <param name="value">The returned value.</param>
255  /// <returns><c>true</c> if evaluation of the path succeeded and the value is valid, <c>false</c> otherwise.</returns>
256  /// <exception cref="System.ArgumentNullException">rootObject</exception>
257  public bool TryGetValue(object rootObject, out object value)
258  {
259  if (rootObject == null) throw new ArgumentNullException("rootObject");
260  value = null;
261  try
262  {
263  object nextObject = rootObject;
264  for (int i = 0; i < items.Count; i++)
265  {
266  var item = items[i];
267  nextObject = item.GetValue(nextObject);
268  }
269  value = nextObject;
270  }
271  catch (Exception)
272  {
273  // If an exception occured, we cannot resolve this member path to a valid property/field
274  return false;
275  }
276  return true;
277  }
278 
279  /// <summary>
280  /// Gets the value from the specified root object following this instance path.
281  /// </summary>
282  /// <param name="rootObject">The root object.</param>
283  /// <param name="value">The returned value.</param>
284  /// <param name="overrideType">Type of the override.</param>
285  /// <returns><c>true</c> if evaluation of the path succeeded and the value is valid, <c>false</c> otherwise.</returns>
286  /// <exception cref="System.ArgumentNullException">rootObject</exception>
287  public bool TryGetValue(object rootObject, out object value, out OverrideType overrideType)
288  {
289  if (rootObject == null) throw new ArgumentNullException("rootObject");
290  if (items.Count == 0) throw new InvalidOperationException("No items pushed via Push methods");
291 
292  value = null;
293  overrideType = OverrideType.Base;
294  try
295  {
296  object nextObject = rootObject;
297 
298  var lastItem = items[items.Count - 1];
299  var memberDescriptor = lastItem.MemberDescriptor;
300 
301  for (int i = 0; i < items.Count - 1; i++)
302  {
303  var item = items[i];
304  nextObject = item.GetValue(nextObject);
305  }
306 
307  overrideType = nextObject.GetOverride(memberDescriptor);
308  value = lastItem.GetValue(nextObject);
309 
310  }
311  catch (Exception)
312  {
313  // If an exception occured, we cannot resolve this member path to a valid property/field
314  return false;
315  }
316  return true;
317  }
318 
319  /// <summary>
320  /// Clones this instance, cloning the current path.
321  /// </summary>
322  /// <returns>A clone of this instance.</returns>
323  public MemberPath Clone()
324  {
325  return new MemberPath(items);
326  }
327 
328  /// <summary>
329  /// Returns a <see cref="System.String" /> that represents this instance.
330  /// </summary>
331  /// <returns>A <see cref="System.String" /> that represents this instance.</returns>
332  public override string ToString()
333  {
334  var text = new StringBuilder();
335  bool isFirst = true;
336  foreach (var memberPathItem in items)
337  {
338  text.Append(memberPathItem.GetName(isFirst));
339  isFirst = false;
340  }
341  return text.ToString();
342  }
343 
344  private void AddItem(MemberPathItem item)
345  {
346  var previousItem = items.Count > 0 ? items[items.Count - 1] : null;
347  items.Add(item);
348  item.Parent = previousItem;
349  }
350 
351  private abstract class MemberPathItem
352  {
353  public MemberPathItem Parent { get; set; }
354 
355  public abstract IMemberDescriptor MemberDescriptor { get; }
356 
357  public abstract object GetValue(object thisObj);
358 
359  public abstract void SetValue(List<object> stack, int objectIndex, object thisObject, object value);
360 
361  public abstract string GetName(bool isFirst);
362  }
363 
364  private sealed class PropertyPathItem : MemberPathItem
365  {
366  private readonly PropertyDescriptor Descriptor;
367 
368  private readonly bool isValueType;
369 
370  public PropertyPathItem(PropertyDescriptor descriptor)
371  {
372  this.Descriptor = descriptor;
373  isValueType = descriptor.DeclaringType.IsValueType;
374  }
375 
376  public override IMemberDescriptor MemberDescriptor
377  {
378  get
379  {
380  return Descriptor;
381  }
382  }
383 
384  public override object GetValue(object thisObj)
385  {
386  return Descriptor.Get(thisObj);
387  }
388 
389  public override void SetValue(List<object> stack, int objectIndex, object thisObject, object value)
390  {
391  Descriptor.Set(thisObject, value);
392 
393  if (isValueType && Parent != null)
394  {
395  Parent.SetValue(stack, objectIndex - 1, stack[objectIndex-1], thisObject);
396  }
397  }
398 
399  public override string GetName(bool isFirst)
400  {
401  return isFirst ? Descriptor.Name : "." + Descriptor.Name;
402  }
403  }
404 
405  private sealed class FieldPathItem : MemberPathItem
406  {
407  private readonly FieldDescriptor Descriptor;
408  private readonly bool isValueType;
409 
410  public FieldPathItem(FieldDescriptor descriptor)
411  {
412  this.Descriptor = descriptor;
413  isValueType = descriptor.DeclaringType.IsValueType;
414  }
415 
416  public override IMemberDescriptor MemberDescriptor
417  {
418  get
419  {
420  return Descriptor;
421  }
422  }
423 
424  public override object GetValue(object thisObj)
425  {
426  return Descriptor.Get(thisObj);
427  }
428 
429  public override void SetValue(List<object> stack, int objectIndex, object thisObject, object value)
430  {
431  Descriptor.Set(thisObject, value);
432 
433  if (isValueType && Parent != null)
434  {
435  Parent.SetValue(stack, objectIndex - 1, stack[objectIndex - 1], thisObject);
436  }
437  }
438 
439  public override string GetName(bool isFirst)
440  {
441  return "." + Descriptor.Name;
442  }
443  }
444 
445  private abstract class SpecialMemberPathItemBase : MemberPathItem
446  {
447  public override IMemberDescriptor MemberDescriptor
448  {
449  get
450  {
451  return null;
452  }
453  }
454  }
455 
456 
457  private sealed class ArrayPathItem : SpecialMemberPathItemBase
458  {
459  private readonly int index;
460 
461  public ArrayPathItem(int index)
462  {
463  this.index = index;
464  }
465 
466  public override object GetValue(object thisObj)
467  {
468  return ((Array)thisObj).GetValue(index);
469  }
470 
471  public override void SetValue(List<object> stack, int objectIndex, object thisObject, object value)
472  {
473  ((Array)thisObject).SetValue(value, index);
474  }
475 
476  public override string GetName(bool isFirst)
477  {
478  return "[" + index + "]";
479  }
480  }
481 
482  private sealed class CollectionPathItem : SpecialMemberPathItemBase
483  {
484  public readonly CollectionDescriptor Descriptor;
485 
486  public readonly int Index;
487 
488  public CollectionPathItem(CollectionDescriptor descriptor, int index)
489  {
490  this.Descriptor = descriptor;
491  this.Index = index;
492  }
493 
494  public override object GetValue(object thisObj)
495  {
496  return Descriptor.GetValue(thisObj, Index);
497  }
498 
499  public override void SetValue(List<object> stack, int objectIndex, object thisObject, object value)
500  {
501  Descriptor.SetValue(thisObject, Index, value);
502  }
503 
504  public override string GetName(bool isFirst)
505  {
506  return "[" + Index + "]";
507  }
508  }
509 
510  private sealed class DictionaryPathItem : SpecialMemberPathItemBase
511  {
512  public readonly DictionaryDescriptor Descriptor;
513 
514  public readonly object Key;
515 
516  public DictionaryPathItem(DictionaryDescriptor descriptor, object key)
517  {
518  this.Descriptor = descriptor;
519  this.Key = key;
520  }
521 
522  public override object GetValue(object thisObj)
523  {
524  return Descriptor.GetValue(thisObj, Key);
525  }
526 
527  public override void SetValue(List<object> stack, int objectIndex, object thisObject, object value)
528  {
529  Descriptor.SetValue(thisObject, Key, value);
530  }
531 
532  public override string GetName(bool isFirst)
533  {
534  return "[" + Key + "]";
535  }
536  }
537  }
538 }
Provides a descriptor for a System.Collections.ICollection.
A IMemberDescriptor for a PropertyInfo
bool TryGetValue(object rootObject, out object value)
Gets the value from the specified root object following this instance path.
Definition: MemberPath.cs:257
void EnsureCapacity(int pathCount)
Ensures the capacity of the paths definition when using Push(SiliconStudio.Core.Reflection.IMemberDescriptor) methods.
Definition: MemberPath.cs:54
void Pop()
Pops the last item from the current path.
Definition: MemberPath.cs:147
SiliconStudio.Core.Reflection.IMemberDescriptor IMemberDescriptor
Provides a descriptor for a System.Collections.IDictionary.
MemberPath()
Initializes a new instance of the MemberPath class.
Definition: MemberPath.cs:26
void Push(CollectionDescriptor descriptor, int index)
Pushes an collection access on the path.
Definition: MemberPath.cs:126
void Push(ArrayDescriptor descriptor, int index)
Pushes an array access on the path.
Definition: MemberPath.cs:114
A IMemberDescriptor for a FieldInfo
Allows to get/set a property/field value on a deeply nested object instance (supporting members...
Definition: MemberPath.cs:14
void Clear()
Clears the current path.
Definition: MemberPath.cs:62
void Push(DictionaryDescriptor descriptor, object key)
Pushes an dictionary access on the path.
Definition: MemberPath.cs:138
MemberPath Clone()
Clones this instance, cloning the current path.
Definition: MemberPath.cs:323
OverrideType
A Type of override used on a member value.
Definition: OverrideType.cs:11
bool TryGetValue(object rootObject, out object value, out OverrideType overrideType)
Gets the value from the specified root object following this instance path.
Definition: MemberPath.cs:287
Base class for IMemberDescriptor for a MemberInfo
void Push(IMemberDescriptor descriptor)
Pushes a member access on the path.
Definition: MemberPath.cs:102
override string ToString()
Returns a System.String that represents this instance.
Definition: MemberPath.cs:332
bool Apply(object rootObject, MemberPathAction actionType, object value)
Definition: MemberPath.cs:155
MemberPathAction
A type of action used by MemberPath.Apply
MemberPath(int capacity)
Initializes a new instance of the MemberPath class.
Definition: MemberPath.cs:34