BsonCreatorMap.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. /* Copyright 2010-present MongoDB Inc.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Linq;
  18. using System.Reflection;
  19. namespace MongoDB.Bson.Serialization
  20. {
  21. /// <summary>
  22. /// Represents a mapping to a delegate and its arguments.
  23. /// </summary>
  24. public class BsonCreatorMap
  25. {
  26. // private fields
  27. private readonly BsonClassMap _classMap;
  28. private readonly MemberInfo _memberInfo; // null if there is no corresponding constructor or factory method
  29. private readonly Delegate _delegate;
  30. private bool _isFrozen;
  31. private List<MemberInfo> _arguments; // the members that define the values for the delegate's parameters
  32. // these values are set when Freeze is called
  33. private List<string> _elementNames; // the element names of the serialized arguments
  34. private Dictionary<string, object> _defaultValues; // not all arguments have default values
  35. // constructors
  36. /// <summary>
  37. /// Initializes a new instance of the BsonCreatorMap class.
  38. /// </summary>
  39. /// <param name="classMap">The class map.</param>
  40. /// <param name="memberInfo">The member info (null if none).</param>
  41. /// <param name="delegate">The delegate.</param>
  42. public BsonCreatorMap(BsonClassMap classMap, MemberInfo memberInfo, Delegate @delegate)
  43. {
  44. if (classMap == null)
  45. {
  46. throw new ArgumentNullException("classMap");
  47. }
  48. if (@delegate == null)
  49. {
  50. throw new ArgumentNullException("delegate");
  51. }
  52. _classMap = classMap;
  53. _memberInfo = memberInfo;
  54. _delegate = @delegate;
  55. }
  56. // public properties
  57. /// <summary>
  58. /// Gets the arguments.
  59. /// </summary>
  60. public IEnumerable<MemberInfo> Arguments
  61. {
  62. get { return _arguments; }
  63. }
  64. /// <summary>
  65. /// Gets the class map that this creator map belongs to.
  66. /// </summary>
  67. public BsonClassMap ClassMap
  68. {
  69. get { return _classMap; }
  70. }
  71. /// <summary>
  72. /// Gets the delegeate
  73. /// </summary>
  74. public Delegate Delegate
  75. {
  76. get { return _delegate; }
  77. }
  78. /// <summary>
  79. /// Gets the element names.
  80. /// </summary>
  81. public IEnumerable<string> ElementNames
  82. {
  83. get
  84. {
  85. if (!_isFrozen) { ThrowNotFrozenException(); }
  86. return _elementNames;
  87. }
  88. }
  89. /// <summary>
  90. /// Gets the member info (null if none).
  91. /// </summary>
  92. public MemberInfo MemberInfo
  93. {
  94. get { return _memberInfo; }
  95. }
  96. // public methods
  97. /// <summary>
  98. /// Freezes the creator map.
  99. /// </summary>
  100. public void Freeze()
  101. {
  102. if (!_isFrozen)
  103. {
  104. var allMemberMaps = _classMap.AllMemberMaps;
  105. var elementNames = new List<string>();
  106. var defaultValues = new Dictionary<string, object>();
  107. var expectedArgumentsCount = GetExpectedArgumentsCount();
  108. if (_arguments != null)
  109. {
  110. if (_arguments.Count != expectedArgumentsCount)
  111. {
  112. throw new BsonSerializationException($"Creator map for class {_classMap.ClassType.FullName} has {expectedArgumentsCount} arguments, not {_arguments.Count}.");
  113. }
  114. foreach (var argument in _arguments)
  115. {
  116. var memberMap = allMemberMaps.FirstOrDefault(m => IsSameMember(m.MemberInfo, argument));
  117. if (memberMap == null)
  118. {
  119. var message = string.Format("Member '{0}' is not mapped.", argument.Name);
  120. throw new BsonSerializationException(message);
  121. }
  122. elementNames.Add(memberMap.ElementName);
  123. if (memberMap.IsDefaultValueSpecified)
  124. {
  125. defaultValues.Add(memberMap.ElementName, memberMap.DefaultValue);
  126. }
  127. }
  128. }
  129. else
  130. {
  131. if (expectedArgumentsCount != 0)
  132. {
  133. throw new BsonSerializationException($"Creator map for class {_classMap.ClassType.FullName} has {expectedArgumentsCount} arguments, but none are configured.");
  134. }
  135. }
  136. _elementNames = elementNames;
  137. _defaultValues = defaultValues;
  138. _isFrozen = true;
  139. }
  140. }
  141. /// <summary>
  142. /// Gets whether there is a default value for a missing element.
  143. /// </summary>
  144. /// <param name="elementName">The element name.</param>
  145. /// <returns>True if there is a default value for element name; otherwise, false.</returns>
  146. public bool HasDefaultValue(string elementName)
  147. {
  148. if (!_isFrozen) { ThrowNotFrozenException(); }
  149. return _defaultValues.ContainsKey(elementName);
  150. }
  151. /// <summary>
  152. /// Sets the arguments for the creator map.
  153. /// </summary>
  154. /// <param name="arguments">The arguments.</param>
  155. /// <returns>The creator map.</returns>
  156. public BsonCreatorMap SetArguments(IEnumerable<MemberInfo> arguments)
  157. {
  158. if (arguments == null)
  159. {
  160. throw new ArgumentNullException("arguments");
  161. }
  162. if (_isFrozen) { ThrowFrozenException(); }
  163. var argumentsList = arguments.ToList(); // only enumerate once
  164. var expectedArgumentsCount = GetExpectedArgumentsCount();
  165. if (argumentsList.Count != expectedArgumentsCount)
  166. {
  167. throw new ArgumentException($"Creator map for class {_classMap.ClassType.FullName} has {expectedArgumentsCount} arguments, not {argumentsList.Count}.", nameof(arguments));
  168. }
  169. _arguments = argumentsList;
  170. return this;
  171. }
  172. /// <summary>
  173. /// Sets the arguments for the creator map.
  174. /// </summary>
  175. /// <param name="argumentNames">The argument names.</param>
  176. /// <returns>The creator map.</returns>
  177. public BsonCreatorMap SetArguments(IEnumerable<string> argumentNames)
  178. {
  179. if (argumentNames == null)
  180. {
  181. throw new ArgumentNullException("argumentNames");
  182. }
  183. if (_isFrozen) { ThrowFrozenException(); }
  184. var classTypeInfo = _classMap.ClassType.GetTypeInfo();
  185. var arguments = new List<MemberInfo>();
  186. foreach (var argumentName in argumentNames)
  187. {
  188. var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
  189. var memberInfos = classTypeInfo.GetMembers(bindingFlags)
  190. .Where(m => m.Name == argumentName && (m is FieldInfo || m is PropertyInfo))
  191. .ToArray();
  192. if (memberInfos.Length == 0)
  193. {
  194. var message = string.Format("Class '{0}' does not have a member named '{1}'.", _classMap.ClassType.FullName, argumentName);
  195. throw new BsonSerializationException(message);
  196. }
  197. else if (memberInfos.Length > 1)
  198. {
  199. var message = string.Format("Class '{0}' has more than one member named '{1}'.", _classMap.ClassType.FullName, argumentName);
  200. throw new BsonSerializationException(message);
  201. }
  202. arguments.Add(memberInfos[0]);
  203. }
  204. SetArguments(arguments);
  205. return this;
  206. }
  207. // internal methods
  208. internal object CreateInstance(Dictionary<string, object> values)
  209. {
  210. var arguments = new List<object>();
  211. // get the values for the arguments to be passed to the creator delegate
  212. foreach (var elementName in _elementNames)
  213. {
  214. object argument;
  215. if (values.TryGetValue(elementName, out argument))
  216. {
  217. values.Remove(elementName);
  218. }
  219. else if (!_defaultValues.TryGetValue(elementName, out argument))
  220. {
  221. // shouldn't happen unless there is a bug in ChooseBestCreator
  222. throw new BsonInternalException();
  223. }
  224. arguments.Add(argument);
  225. }
  226. return _delegate.DynamicInvoke(arguments.ToArray());
  227. }
  228. // private methods
  229. private int GetExpectedArgumentsCount()
  230. {
  231. var constructorInfo = _memberInfo as ConstructorInfo;
  232. if (constructorInfo != null)
  233. {
  234. return constructorInfo.GetParameters().Length;
  235. }
  236. var methodInfo = _memberInfo as MethodInfo;
  237. if (methodInfo != null)
  238. {
  239. return methodInfo.GetParameters().Length;
  240. }
  241. var delegateParameters = _delegate.GetMethodInfo().GetParameters();
  242. if (delegateParameters.Length == 0)
  243. {
  244. return 0;
  245. }
  246. else
  247. {
  248. // check if delegate is closed over its first parameter
  249. if (_delegate.Target != null && _delegate.Target.GetType() == delegateParameters[0].ParameterType)
  250. {
  251. return delegateParameters.Length - 1;
  252. }
  253. else
  254. {
  255. return delegateParameters.Length;
  256. }
  257. }
  258. }
  259. private bool IsSameMember(MemberInfo a, MemberInfo b)
  260. {
  261. // two MemberInfos refer to the same member if the Module and MetadataToken are equal
  262. return a.Module == b.Module && a.MetadataToken == b.MetadataToken;
  263. }
  264. private void ThrowFrozenException()
  265. {
  266. throw new InvalidOperationException("BsonCreatorMap is frozen.");
  267. }
  268. private void ThrowNotFrozenException()
  269. {
  270. throw new InvalidOperationException("BsonCreatorMap is not frozen.");
  271. }
  272. }
  273. }