PropertyTableAssigner.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using MoonSharp.Interpreter.Compatibility;
  6. namespace MoonSharp.Interpreter.Interop
  7. {
  8. /// <summary>
  9. /// Utility class which may be used to set properties on an object of type T, from values contained in a Lua table.
  10. /// Properties must be decorated with the <see cref="MoonSharpPropertyAttribute"/>.
  11. /// This is a generic version of <see cref="PropertyTableAssigner"/>.
  12. /// </summary>
  13. /// <typeparam name="T">The type of the object.</typeparam>
  14. public class PropertyTableAssigner<T> : IPropertyTableAssigner
  15. {
  16. PropertyTableAssigner m_InternalAssigner;
  17. /// <summary>
  18. /// Initializes a new instance of the <see cref="PropertyTableAssigner{T}"/> class.
  19. /// </summary>
  20. /// <param name="expectedMissingProperties">The expected missing properties, that is expected fields in the table with no corresponding property in the object.</param>
  21. public PropertyTableAssigner(params string[] expectedMissingProperties)
  22. {
  23. m_InternalAssigner = new PropertyTableAssigner(typeof(T), expectedMissingProperties);
  24. }
  25. /// <summary>
  26. /// Adds an expected missing property, that is an expected field in the table with no corresponding property in the object.
  27. /// </summary>
  28. /// <param name="name">The name.</param>
  29. public void AddExpectedMissingProperty(string name)
  30. {
  31. m_InternalAssigner.AddExpectedMissingProperty(name);
  32. }
  33. /// <summary>
  34. /// Assigns properties from tables to an object.
  35. /// </summary>
  36. /// <param name="obj">The object.</param>
  37. /// <param name="data">The table.</param>
  38. /// <exception cref="System.ArgumentNullException">Object is null</exception>
  39. /// <exception cref="ScriptRuntimeException">A field does not correspond to any property and that property is not one of the expected missing ones.</exception>
  40. public void AssignObject(T obj, Table data)
  41. {
  42. m_InternalAssigner.AssignObject(obj, data);
  43. }
  44. /// <summary>
  45. /// Gets the type-unsafe assigner corresponding to this object.
  46. /// </summary>
  47. /// <returns></returns>
  48. public PropertyTableAssigner GetTypeUnsafeAssigner()
  49. {
  50. return m_InternalAssigner;
  51. }
  52. /// <summary>
  53. /// Sets the subassigner for the given type. Pass null to remove usage of subassigner for the given type.
  54. /// </summary>
  55. /// <param name="propertyType">Type of the property for which the subassigner will be used.</param>
  56. /// <param name="assigner">The property assigner.</param>
  57. public void SetSubassignerForType(Type propertyType, IPropertyTableAssigner assigner)
  58. {
  59. m_InternalAssigner.SetSubassignerForType(propertyType, assigner);
  60. }
  61. /// <summary>
  62. /// Sets the subassigner for the given type
  63. /// </summary>
  64. /// <typeparam name="SubassignerType">Type of the property for which the subassigner will be used.</typeparam>
  65. /// <param name="assigner">The property assigner.</param>
  66. public void SetSubassigner<SubassignerType>(PropertyTableAssigner<SubassignerType> assigner)
  67. {
  68. m_InternalAssigner.SetSubassignerForType(typeof(SubassignerType), assigner);
  69. }
  70. /// <summary>
  71. /// Assigns the properties of the specified object without checking the type.
  72. /// </summary>
  73. /// <param name="o">The object.</param>
  74. /// <param name="data">The data.</param>
  75. void IPropertyTableAssigner.AssignObjectUnchecked(object o, Table data)
  76. {
  77. AssignObject((T)o, data);
  78. }
  79. }
  80. /// <summary>
  81. /// Utility class which may be used to set properties on an object from values contained in a Lua table.
  82. /// Properties must be decorated with the <see cref="MoonSharpPropertyAttribute"/>.
  83. /// See <see cref="PropertyTableAssigner{T}"/> for a generic compile time type-safe version.
  84. /// </summary>
  85. public class PropertyTableAssigner : IPropertyTableAssigner
  86. {
  87. Type m_Type;
  88. Dictionary<string, PropertyInfo> m_PropertyMap = new Dictionary<string, PropertyInfo>();
  89. Dictionary<Type, IPropertyTableAssigner> m_SubAssigners = new Dictionary<Type, IPropertyTableAssigner>();
  90. /// <summary>
  91. /// Initializes a new instance of the <see cref="PropertyTableAssigner"/> class.
  92. /// </summary>
  93. /// <param name="type">The type of the object.</param>
  94. /// <param name="expectedMissingProperties">The expected missing properties, that is expected fields in the table with no corresponding property in the object.</param>
  95. /// <exception cref="System.ArgumentException">
  96. /// Type cannot be a value type.
  97. /// </exception>
  98. public PropertyTableAssigner(Type type, params string[] expectedMissingProperties)
  99. {
  100. m_Type = type;
  101. if (Framework.Do.IsValueType(m_Type))
  102. throw new ArgumentException("Type cannot be a value type.");
  103. foreach(string property in expectedMissingProperties)
  104. {
  105. m_PropertyMap.Add(property, null);
  106. }
  107. foreach (PropertyInfo pi in Framework.Do.GetProperties(m_Type))
  108. {
  109. foreach (MoonSharpPropertyAttribute attr in pi.GetCustomAttributes(true).OfType<MoonSharpPropertyAttribute>())
  110. {
  111. string name = attr.Name ?? pi.Name;
  112. if (m_PropertyMap.ContainsKey(name))
  113. {
  114. throw new ArgumentException(string.Format("Type {0} has two definitions for MoonSharp property {1}", m_Type.FullName, name));
  115. }
  116. else
  117. {
  118. m_PropertyMap.Add(name, pi);
  119. }
  120. }
  121. }
  122. }
  123. /// <summary>
  124. /// Adds an expected missing property, that is an expected field in the table with no corresponding property in the object.
  125. /// </summary>
  126. /// <param name="name">The name.</param>
  127. public void AddExpectedMissingProperty(string name)
  128. {
  129. m_PropertyMap.Add(name, null);
  130. }
  131. private bool TryAssignProperty(object obj, string name, DynValue value)
  132. {
  133. if (m_PropertyMap.ContainsKey(name))
  134. {
  135. PropertyInfo pi = m_PropertyMap[name];
  136. if (pi != null)
  137. {
  138. object o;
  139. if (value.Type == DataType.Table && m_SubAssigners.ContainsKey(pi.PropertyType))
  140. {
  141. var subassigner = m_SubAssigners[pi.PropertyType];
  142. o = Activator.CreateInstance(pi.PropertyType);
  143. subassigner.AssignObjectUnchecked(o, value.Table);
  144. }
  145. else
  146. {
  147. o = Interop.Converters.ScriptToClrConversions.DynValueToObjectOfType(value,
  148. pi.PropertyType, null, false);
  149. }
  150. Framework.Do.GetSetMethod(pi).Invoke(obj, new object[] { o });
  151. }
  152. return true;
  153. }
  154. return false;
  155. }
  156. private void AssignProperty(object obj, string name, DynValue value)
  157. {
  158. if (TryAssignProperty(obj, name, value)) return;
  159. if ((Script.GlobalOptions.FuzzySymbolMatching & FuzzySymbolMatchingBehavior.UpperFirstLetter) == FuzzySymbolMatchingBehavior.UpperFirstLetter && TryAssignProperty(obj, DescriptorHelpers.UpperFirstLetter(name), value)) return;
  160. if ((Script.GlobalOptions.FuzzySymbolMatching & FuzzySymbolMatchingBehavior.Camelify) == FuzzySymbolMatchingBehavior.Camelify && TryAssignProperty(obj, DescriptorHelpers.Camelify(name), value)) return;
  161. if ((Script.GlobalOptions.FuzzySymbolMatching & FuzzySymbolMatchingBehavior.PascalCase) == FuzzySymbolMatchingBehavior.PascalCase && TryAssignProperty(obj, DescriptorHelpers.UpperFirstLetter(DescriptorHelpers.Camelify(name)), value)) return;
  162. throw new ScriptRuntimeException("Invalid property {0}", name);
  163. }
  164. /// <summary>
  165. /// Assigns properties from tables to an object.
  166. /// </summary>
  167. /// <param name="obj">The object.</param>
  168. /// <param name="data">The table.</param>
  169. /// <exception cref="System.ArgumentNullException">Object is null</exception>
  170. /// <exception cref="System.ArgumentException">The object is of an incompatible type.</exception>
  171. /// <exception cref="ScriptRuntimeException">A field does not correspond to any property and that property is not one of the expected missing ones.</exception>
  172. public void AssignObject(object obj, Table data)
  173. {
  174. if (obj == null)
  175. throw new ArgumentNullException("Object is null");
  176. if (!Framework.Do.IsInstanceOfType(m_Type, obj))
  177. throw new ArgumentException(string.Format("Invalid type of object : got '{0}', expected {1}", obj.GetType().FullName, m_Type.FullName));
  178. foreach (var pair in data.Pairs)
  179. {
  180. if (pair.Key.Type != DataType.String)
  181. {
  182. throw new ScriptRuntimeException("Invalid property of type {0}", pair.Key.Type.ToErrorTypeString());
  183. }
  184. AssignProperty(obj, pair.Key.String, pair.Value);
  185. }
  186. }
  187. /// <summary>
  188. /// Sets the subassigner for the given type. Pass null to remove usage of subassigner for the given type.
  189. /// </summary>
  190. /// <param name="propertyType">Type of the property for which the subassigner will be used.</param>
  191. /// <param name="assigner">The property assigner.</param>
  192. public void SetSubassignerForType(Type propertyType, IPropertyTableAssigner assigner)
  193. {
  194. if ( Framework.Do.IsAbstract(propertyType)
  195. || Framework.Do.IsGenericType(propertyType)
  196. || Framework.Do.IsInterface(propertyType)
  197. || Framework.Do.IsValueType(propertyType))
  198. {
  199. throw new ArgumentException("propertyType must be a concrete, reference type");
  200. }
  201. m_SubAssigners[propertyType] = assigner;
  202. }
  203. /// <summary>
  204. /// Assigns the properties of the specified object without checking the type.
  205. /// </summary>
  206. /// <param name="o">The object.</param>
  207. /// <param name="data">The data.</param>
  208. void IPropertyTableAssigner.AssignObjectUnchecked(object obj, Table data)
  209. {
  210. this.AssignObject(obj, data);
  211. }
  212. }
  213. /// <summary>
  214. /// Common interface for property assigners - basically used for sub-assigners
  215. /// </summary>
  216. public interface IPropertyTableAssigner
  217. {
  218. /// <summary>
  219. /// Assigns the properties of the specified object without checking the type.
  220. /// </summary>
  221. /// <param name="o">The object.</param>
  222. /// <param name="data">The data.</param>
  223. void AssignObjectUnchecked(object o, Table data);
  224. }
  225. }