PageRenderTime 53ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/Framework/src/Ncqrs/Commanding/CommandExecution/Mapping/PropertiesToMethodMapper.cs

https://github.com/elfrostie/ncqrs
C# | 230 lines | 179 code | 43 blank | 8 comment | 30 complexity | 4b62c14826bf36caf87f895015496b1f MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics.Contracts;
  4. using System.Linq;
  5. using System.Reflection;
  6. using Ncqrs.Commanding.CommandExecution.Mapping.Attributes;
  7. namespace Ncqrs.Commanding.CommandExecution.Mapping
  8. {
  9. public class PropertiesToMethodMapper
  10. {
  11. private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  12. private static BindingFlags All = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
  13. public static Tuple<ConstructorInfo, PropertyInfo[]> GetConstructor(PropertyToParameterMappingInfo[] sources, Type targetType)
  14. {
  15. var potentialTargets = targetType.GetConstructors(All);
  16. return GetMethodBase(sources, potentialTargets);
  17. }
  18. public static Tuple<MethodInfo, PropertyInfo[]> GetMethod(PropertyToParameterMappingInfo[] sources, Type targetType, BindingFlags bindingFlags, string methodName = null)
  19. {
  20. IEnumerable<MethodInfo> potentialTargets = targetType.GetMethods(bindingFlags);
  21. if (methodName != null)
  22. {
  23. potentialTargets = potentialTargets.Where
  24. (
  25. method => method.Name.Equals(methodName, StringComparison.InvariantCultureIgnoreCase)
  26. );
  27. }
  28. return GetMethodBase(sources, potentialTargets);
  29. }
  30. public static Tuple<MethodInfo, PropertyInfo[]> GetMethod(PropertyToParameterMappingInfo[] sources, Type targetType, string methodName = null)
  31. {
  32. IEnumerable<MethodInfo> potentialTargets = targetType.GetMethods(All);
  33. if(methodName != null)
  34. {
  35. potentialTargets = potentialTargets.Where
  36. (
  37. method => method.Name.Equals(methodName, StringComparison.InvariantCultureIgnoreCase)
  38. );
  39. }
  40. return GetMethodBase(sources, potentialTargets);
  41. }
  42. private static Tuple<TMethodBase, PropertyInfo[]> GetMethodBase<TMethodBase>(PropertyToParameterMappingInfo[] sources, IEnumerable<TMethodBase> potentialTargets)
  43. where TMethodBase : MethodBase
  44. {
  45. var propertiesToMap = new List<PropertyToParameterMappingInfo>(sources);
  46. var mappedProps = new PropertyInfo[propertiesToMap.Count];
  47. var targets = new List<MethodBase>(potentialTargets);
  48. MakeSureAllPropertiesToMapOnNameHaveUniqueNames(propertiesToMap);
  49. MakeSureAllPropertieOrdinalsAreUnique(propertiesToMap);
  50. // Remove all targets that do no match the parameter count.
  51. targets.RemoveAll(t=>!HasCorrectParameterCount(t, propertiesToMap.Count));
  52. if (targets.IsEmpty())
  53. {
  54. var msg = string.Format("No target found that accepts {0} parameter(s).",
  55. propertiesToMap.Count);
  56. throw new CommandMappingException(msg);
  57. }
  58. AddOrdinalMappedProperties(mappedProps, propertiesToMap);
  59. targets.RemoveAll(t => !IsTargetInvokableFromKnownProperties(t, mappedProps));
  60. if (targets.Count == 0)
  61. {
  62. // TODO: Throw proper ex.
  63. throw new CommandMappingException("No target found that matches the mapping.");
  64. }
  65. var matches = FilterCtorTargetsOnNameMappedProperties(targets, mappedProps, propertiesToMap);
  66. if (matches.Count() == 0)
  67. {
  68. // TODO: Throw proper ex.
  69. throw new CommandMappingException("No target on found that matches the mapping.");
  70. }
  71. else if (matches.Count() > 1)
  72. {
  73. // TODO: Throw proper ex.
  74. throw new CommandMappingException("Multi targets on found that matches the mapping.");
  75. }
  76. var match = matches.Single();
  77. return new Tuple<TMethodBase, PropertyInfo[]>((TMethodBase)match.Item1, match.Item2);
  78. }
  79. private static void MakeSureAllPropertieOrdinalsAreUnique(List<PropertyToParameterMappingInfo> propertiesToMap)
  80. {
  81. Contract.Requires<ArgumentNullException>(propertiesToMap != null);
  82. var query = from p in propertiesToMap
  83. where p.Ordinal.HasValue
  84. group p by p.Ordinal
  85. into g
  86. where g.Count() > 1
  87. select g.First();
  88. if (query.Count() > 0)
  89. {
  90. var firstDuplicate = query.First();
  91. throw new CommandMappingException("Cannot map multiple properties with the same name " + firstDuplicate.TargetName + ".");
  92. }
  93. }
  94. private static void MakeSureAllPropertiesToMapOnNameHaveUniqueNames(List<PropertyToParameterMappingInfo> propertiesToMap)
  95. {
  96. Contract.Requires<ArgumentNullException>(propertiesToMap != null);
  97. var query = from p in propertiesToMap
  98. where !p.TargetName.IsNullOrEmpty()
  99. group p by p.TargetName
  100. into g
  101. where g.Count() > 1
  102. select g.First();
  103. if (query.Count() > 0)
  104. {
  105. var firstDuplicate = query.First();
  106. // TODO: Better exception.)
  107. throw new CommandMappingException("Cannot map multiple properties with the same name " + firstDuplicate.TargetName +
  108. ".");
  109. }
  110. }
  111. private static List<Tuple<MethodBase, PropertyInfo[]>> FilterCtorTargetsOnNameMappedProperties(List<MethodBase> potentialTargets, PropertyInfo[] mappedProps, List<PropertyToParameterMappingInfo> propertiesToMap)
  112. {
  113. var result = new List<Tuple<MethodBase, PropertyInfo[]>>();
  114. foreach (var method in potentialTargets)
  115. {
  116. var parameters = method.GetParameters();
  117. var mapped = new List<PropertyInfo>(mappedProps);
  118. var toMap = propertiesToMap.Clone();
  119. for (int i = 0; i < parameters.Length; i++)
  120. {
  121. var prop = mapped[i];
  122. var param = parameters[i];
  123. if (prop == null)
  124. {
  125. var matchedOnName = toMap.SingleOrDefault
  126. (
  127. p => p.TargetName.Equals(param.Name, StringComparison.InvariantCultureIgnoreCase)
  128. );
  129. if (matchedOnName != null)
  130. {
  131. mapped[i] = matchedOnName.Property;
  132. toMap.Remove(matchedOnName);
  133. }
  134. }
  135. }
  136. if (!mapped.Exists(p => p == null))
  137. {
  138. result.Add(new Tuple<MethodBase, PropertyInfo[]>(method, mapped.ToArray()));
  139. }
  140. }
  141. return result;
  142. }
  143. private static bool IsTargetInvokableFromKnownProperties(MethodBase target, PropertyInfo[] mappedProps)
  144. {
  145. bool isInvokable = true;
  146. var parameters = target.GetParameters();
  147. for (int i = 0; i < mappedProps.Length; i++)
  148. {
  149. var prop = mappedProps[i];
  150. var param = parameters[i];
  151. if (prop != null)
  152. {
  153. bool isAssignable = param.ParameterType.IsAssignableFrom(prop.PropertyType);
  154. isInvokable &= isAssignable;
  155. }
  156. }
  157. return isInvokable;
  158. }
  159. private static bool HasCorrectParameterCount(MethodBase target, int parameterCount)
  160. {
  161. return target.GetParameters().Length == parameterCount;
  162. }
  163. private static void AddOrdinalMappedProperties(PropertyInfo[] mappedProps, List<PropertyToParameterMappingInfo> propertiesToMap)
  164. {
  165. for (int i = 0; i < propertiesToMap.Count; i++)
  166. {
  167. var prop = propertiesToMap[i];
  168. if (prop.Ordinal.HasValue)
  169. {
  170. // TODO: Throw ordinal out of range exception if needed.
  171. int idx = prop.Ordinal.Value - 1;
  172. if (mappedProps[idx] != null)
  173. throw new ApplicationException(); // TODO: Throw if already mapped.
  174. mappedProps[idx] = prop.Property;
  175. // Remove property since we mapped it and decrease index
  176. // variables so that the next property will also be mapped.
  177. propertiesToMap.RemoveAt(i);
  178. i--;
  179. }
  180. }
  181. }
  182. private static ParameterAttribute GetParameterAttribute(PropertyInfo prop)
  183. {
  184. return (ParameterAttribute)prop.GetCustomAttributes(typeof(ParameterAttribute), false).SingleOrDefault();
  185. }
  186. }
  187. }