PageRenderTime 49ms CodeModel.GetById 35ms app.highlight 10ms 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
  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}