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