DefaultTypeConverter.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. using System;
  2. using System.Collections.ObjectModel;
  3. using System.Linq;
  4. using System.Linq.Expressions;
  5. using Jint.Native;
  6. using System.Collections.Generic;
  7. using System.Reflection;
  8. namespace Jint.Runtime.Interop
  9. {
  10. public class DefaultTypeConverter : ITypeConverter
  11. {
  12. private readonly Engine _engine;
  13. private static readonly Dictionary<string, bool> _knownConversions = new Dictionary<string, bool>();
  14. private static readonly object _lockObject = new object();
  15. private static MethodInfo convertChangeType = typeof(System.Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type), typeof(IFormatProvider) });
  16. private static MethodInfo jsValueFromObject = typeof(JsValue).GetMethod("FromObject");
  17. private static MethodInfo jsValueToObject = typeof(JsValue).GetMethod("ToObject");
  18. public DefaultTypeConverter(Engine engine)
  19. {
  20. _engine = engine;
  21. }
  22. public virtual object Convert(object value, Type type, IFormatProvider formatProvider)
  23. {
  24. if (value == null)
  25. {
  26. if (TypeConverter.TypeIsNullable(type))
  27. {
  28. return null;
  29. }
  30. throw new NotSupportedException(string.Format("Unable to convert null to '{0}'", type.FullName));
  31. }
  32. // don't try to convert if value is derived from type
  33. if (type.IsInstanceOfType(value))
  34. {
  35. return value;
  36. }
  37. if (type.IsEnum())
  38. {
  39. var integer = System.Convert.ChangeType(value, typeof(int), formatProvider);
  40. if (integer == null)
  41. {
  42. throw new ArgumentOutOfRangeException();
  43. }
  44. return Enum.ToObject(type, integer);
  45. }
  46. var valueType = value.GetType();
  47. // is the javascript value an ICallable instance ?
  48. if (valueType == typeof(Func<JsValue, JsValue[], JsValue>))
  49. {
  50. var function = (Func<JsValue, JsValue[], JsValue>)value;
  51. if (type.IsGenericType())
  52. {
  53. var genericType = type.GetGenericTypeDefinition();
  54. // create the requested Delegate
  55. if (genericType.Name.StartsWith("Action"))
  56. {
  57. var genericArguments = type.GetGenericArguments();
  58. var @params = new ParameterExpression[genericArguments.Count()];
  59. for (var i = 0; i < @params.Count(); i++)
  60. {
  61. @params[i] = Expression.Parameter(genericArguments[i], genericArguments[i].Name + i);
  62. }
  63. var tmpVars = new Expression[@params.Length];
  64. for (var i = 0; i < @params.Count(); i++)
  65. {
  66. var param = @params[i];
  67. if (param.Type.IsValueType())
  68. {
  69. var boxing = Expression.Convert(param, typeof(object));
  70. tmpVars[i] = Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, typeof(Engine)), boxing);
  71. }
  72. else
  73. {
  74. tmpVars[i] = Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, typeof(Engine)), param);
  75. }
  76. }
  77. var @vars = Expression.NewArrayInit(typeof(JsValue), tmpVars);
  78. var callExpresion = Expression.Block(Expression.Call(
  79. Expression.Call(Expression.Constant(function.Target),
  80. function.GetMethodInfo(),
  81. Expression.Constant(JsValue.Undefined, typeof(JsValue)),
  82. @vars),
  83. jsValueToObject), Expression.Empty());
  84. return Expression.Lambda(callExpresion, new ReadOnlyCollection<ParameterExpression>(@params)).Compile();
  85. }
  86. else if (genericType.Name.StartsWith("Func"))
  87. {
  88. var genericArguments = type.GetGenericArguments();
  89. var returnType = genericArguments.Last();
  90. var @params = new ParameterExpression[genericArguments.Count() - 1];
  91. for (var i = 0; i < @params.Count(); i++)
  92. {
  93. @params[i] = Expression.Parameter(genericArguments[i], genericArguments[i].Name + i);
  94. }
  95. var @vars =
  96. Expression.NewArrayInit(typeof(JsValue),
  97. @params.Select(p =>
  98. {
  99. var boxingExpression = Expression.Convert(p, typeof(object));
  100. return Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, typeof(Engine)), boxingExpression);
  101. })
  102. );
  103. // the final result's type needs to be changed before casting,
  104. // for instance when a function returns a number (double) but C# expects an integer
  105. var callExpresion = Expression.Convert(
  106. Expression.Call(null,
  107. convertChangeType,
  108. Expression.Call(
  109. Expression.Call(Expression.Constant(function.Target),
  110. function.GetMethodInfo(),
  111. Expression.Constant(JsValue.Undefined, typeof(JsValue)),
  112. @vars),
  113. jsValueToObject),
  114. Expression.Constant(returnType, typeof(Type)),
  115. Expression.Constant(System.Globalization.CultureInfo.InvariantCulture, typeof(IFormatProvider))
  116. ),
  117. returnType);
  118. return Expression.Lambda(callExpresion, new ReadOnlyCollection<ParameterExpression>(@params)).Compile();
  119. }
  120. }
  121. else
  122. {
  123. if (type == typeof(Action))
  124. {
  125. return (Action)(() => function(JsValue.Undefined, new JsValue[0]));
  126. }
  127. else if (typeof(MulticastDelegate).IsAssignableFrom(type))
  128. {
  129. var method = type.GetMethod("Invoke");
  130. var arguments = method.GetParameters();
  131. var @params = new ParameterExpression[arguments.Count()];
  132. for (var i = 0; i < @params.Count(); i++)
  133. {
  134. @params[i] = Expression.Parameter(typeof(object), arguments[i].Name);
  135. }
  136. var @vars = Expression.NewArrayInit(typeof(JsValue), @params.Select(p => Expression.Call(null, typeof(JsValue).GetMethod("FromObject"), Expression.Constant(_engine, typeof(Engine)), p)));
  137. var callExpression = Expression.Block(
  138. Expression.Call(
  139. Expression.Call(Expression.Constant(function.Target),
  140. function.GetMethodInfo(),
  141. Expression.Constant(JsValue.Undefined, typeof(JsValue)),
  142. @vars),
  143. typeof(JsValue).GetMethod("ToObject")),
  144. Expression.Empty());
  145. var dynamicExpression = Expression.Invoke(Expression.Lambda(callExpression, new ReadOnlyCollection<ParameterExpression>(@params)), new ReadOnlyCollection<ParameterExpression>(@params));
  146. return Expression.Lambda(type, dynamicExpression, new ReadOnlyCollection<ParameterExpression>(@params)).Compile();
  147. }
  148. }
  149. }
  150. if (type.IsArray)
  151. {
  152. var source = value as object[];
  153. if (source == null)
  154. throw new ArgumentException(String.Format("Value of object[] type is expected, but actual type is {0}.", value.GetType()));
  155. var targetElementType = type.GetElementType();
  156. var itemsConverted = source.Select(o => Convert(o, targetElementType, formatProvider)).ToArray();
  157. var result = Array.CreateInstance(targetElementType, source.Length);
  158. itemsConverted.CopyTo(result, 0);
  159. return result;
  160. }
  161. if (type.IsGenericType() && type.GetGenericTypeDefinition() == typeof(Nullable<>))
  162. {
  163. type = Nullable.GetUnderlyingType(type);
  164. }
  165. return System.Convert.ChangeType(value, type, formatProvider);
  166. }
  167. public virtual bool TryConvert(object value, Type type, IFormatProvider formatProvider, out object converted)
  168. {
  169. bool canConvert;
  170. var key = value == null ? String.Format("Null->{0}", type) : String.Format("{0}->{1}", value.GetType(), type);
  171. if (!_knownConversions.TryGetValue(key, out canConvert))
  172. {
  173. lock (_lockObject)
  174. {
  175. if (!_knownConversions.TryGetValue(key, out canConvert))
  176. {
  177. try
  178. {
  179. converted = Convert(value, type, formatProvider);
  180. _knownConversions.Add(key, true);
  181. return true;
  182. }
  183. catch
  184. {
  185. converted = null;
  186. _knownConversions.Add(key, false);
  187. return false;
  188. }
  189. }
  190. }
  191. }
  192. if (canConvert)
  193. {
  194. try
  195. {
  196. converted = Convert(value, type, formatProvider);
  197. return true;
  198. }
  199. catch
  200. {
  201. converted = null;
  202. return false;
  203. }
  204. }
  205. converted = null;
  206. return false;
  207. }
  208. }
  209. }