PageRenderTime 47ms CodeModel.GetById 16ms app.highlight 21ms RepoModel.GetById 2ms app.codeStats 0ms

/src/LinFu.IoC/Configuration/Resolvers/MethodFinder.cs

http://github.com/philiplaureano/LinFu
C# | 208 lines | 119 code | 31 blank | 58 comment | 15 complexity | e09b2aebfa37f65dd27f0c5c4d3e9fd9 MD5 | raw file
  1using System;
  2using System.Collections.Generic;
  3using System.Linq;
  4using System.Reflection;
  5using LinFu.Finders;
  6using LinFu.Finders.Interfaces;
  7using LinFu.IoC.Configuration.Interfaces;
  8using LinFu.IoC.Interfaces;
  9
 10namespace LinFu.IoC.Configuration
 11{
 12    /// <summary>
 13    ///     Represents a class that determines which method best matches the
 14    ///     services currently in the target container.
 15    /// </summary>
 16    /// <typeparam name="T">The method type to search.</typeparam>
 17    public class MethodFinder<T> : IMethodFinder<T>
 18        where T : MethodBase
 19    {
 20        /// <summary>
 21        ///     Determines which method best matches the
 22        ///     services currently in the target container.
 23        /// </summary>
 24        /// <param name="items">The list of methods to search.</param>
 25        /// <param name="finderContext">The <see cref="IMethodFinderContext" /> that describes the target method.</param>
 26        /// <returns>
 27        ///     Returns the method with the most resolvable parameters from the target <see cref="IServiceContainer" />
 28        ///     instance.
 29        /// </returns>
 30        public T GetBestMatch(IEnumerable<T> items, IMethodFinderContext finderContext)
 31        {
 32            T bestMatch = null;
 33            var fuzzyList = items.AsFuzzyList();
 34
 35            // Return the first item
 36            // if there is no other alternative
 37            if (fuzzyList.Count == 1)
 38                return fuzzyList[0].Item;
 39
 40            var additionalArguments = finderContext.Arguments;
 41            var additionalArgumentTypes = (from argument in additionalArguments
 42                let argumentType =
 43                    argument == null ? typeof(object) : argument.GetType()
 44                select argumentType).ToList();
 45
 46            Rank(fuzzyList, finderContext);
 47
 48            // Match the generic parameter types
 49            var genericParameterCount = finderContext.TypeArguments != null ? finderContext.TypeArguments.Count() : 0;
 50            Func<T, bool> matchGenericArgumentCount = method =>
 51            {
 52                var genericArguments = method.IsGenericMethod
 53                    ? method.GetGenericArguments
 54                        ()
 55                    : new Type[0];
 56                var currentParameterCount = genericArguments.Count();
 57
 58                return genericParameterCount == currentParameterCount;
 59            };
 60            fuzzyList.AddCriteria(matchGenericArgumentCount, CriteriaType.Critical);
 61
 62            var candidates = fuzzyList.Where(fuzzy => fuzzy.Confidence > 0);
 63            bestMatch = SelectBestMatch(candidates);
 64
 65            // If all else fails, find the method
 66            // that matches only the additional arguments
 67            if (bestMatch != null)
 68                return bestMatch;
 69
 70            return GetNextBestMatch(fuzzyList, additionalArgumentTypes, bestMatch);
 71        }
 72
 73
 74        private T GetNextBestMatch(IList<IFuzzyItem<T>> fuzzyList, List<Type> additionalArgumentTypes, T bestMatch)
 75        {
 76            var additionalArgumentCount = additionalArgumentTypes.Count;
 77            fuzzyList.Reset();
 78
 79            // Match the number of arguments
 80            Func<T, bool> matchParameterCount = method =>
 81            {
 82                var parameters = method.GetParameters();
 83                var parameterCount = parameters != null
 84                    ? parameters.Length
 85                    : 0;
 86
 87                return parameterCount == additionalArgumentCount;
 88            };
 89
 90            // Remove any methods that do not match
 91            // the parameter count
 92            fuzzyList.AddCriteria(matchParameterCount, CriteriaType.Critical);
 93
 94            CheckArguments(fuzzyList, additionalArgumentTypes);
 95            var nextBestMatch = fuzzyList.BestMatch();
 96
 97            if (nextBestMatch == null)
 98                return null;
 99
100            return nextBestMatch.Item;
101        }
102
103        /// <summary>
104        ///     Determines which item among the <paramref name="candidates" /> is the best match.
105        /// </summary>
106        /// <param name="candidates">The list of possible matches.</param>
107        /// <returns>The best match if found; otherwise, it should return <c>null</c>.</returns>
108        protected virtual T SelectBestMatch(IEnumerable<IFuzzyItem<T>> candidates)
109        {
110            var bestMatch = default(T);
111            // Since the remaining constructors all have
112            // parameter types that currently exist
113            // in the container as a service,
114            // the best match will be the constructor with
115            // the most parameters
116            var bestParameterCount = -1;
117            foreach (var candidate in candidates)
118            {
119                var currentItem = candidate.Item;
120                var parameters = currentItem.GetParameters();
121                var parameterCount = parameters.Count();
122
123                if (parameterCount <= bestParameterCount)
124                    continue;
125
126                bestMatch = currentItem;
127                bestParameterCount = parameterCount;
128            }
129
130            return bestMatch;
131        }
132
133        /// <summary>
134        ///     Adds additional <see cref="ICriteria{T}" /> to the fuzzy search list.
135        /// </summary>
136        /// <param name="methods">The list of methods to rank.</param>
137        /// <param name="finderContext">The <see cref="IMethodFinderContext" /> that describes the target method.</param>
138        protected virtual void Rank(IList<IFuzzyItem<T>> methods, IMethodFinderContext finderContext)
139        {
140        }
141
142        /// <summary>
143        ///     Attempts to match the <paramref name="additionalArgumentTypes" /> against the
144        ///     <paramref name="fuzzyList">list of methods</paramref>.
145        /// </summary>
146        /// <param name="fuzzyList">The list of items currently being compared.</param>
147        /// <param name="additionalArgumentTypes">
148        ///     The set of <see cref="Type" /> instances that describe each supplied argument
149        ///     type.
150        /// </param>
151        private static void CheckArguments(IList<IFuzzyItem<T>> fuzzyList,
152            IEnumerable<Type> additionalArgumentTypes)
153        {
154            var argTypes = additionalArgumentTypes.ToArray();
155
156            for (var i = 1; i <= argTypes.Length; i++)
157            {
158                var currentOffset = i;
159                var currentIndex = argTypes.Length - currentOffset;
160                var currentArgumentType = argTypes[currentIndex];
161                Func<T, bool> hasCompatibleArgument = method =>
162                {
163                    var parameters = method.GetParameters();
164                    var parameterCount = parameters.Length;
165                    var targetParameterIndex = currentIndex;
166
167                    // Make sure that the index is valid
168                    if (targetParameterIndex < 0 ||
169                        targetParameterIndex >= parameterCount)
170                        return false;
171
172                    // The parameter type must be compatible with the
173                    // given argument type
174                    var parameterType =
175                        parameters[targetParameterIndex].ParameterType;
176                    return parameterType.IsAssignableFrom(currentArgumentType);
177                };
178
179                // Match each additional argument type to its
180                // relative position from the end of the parameter
181                // list
182                fuzzyList.AddCriteria(hasCompatibleArgument, CriteriaType.Critical);
183
184                Func<T, bool> hasExactArgumentType = method =>
185                {
186                    var parameters = method.GetParameters();
187                    var parameterCount = parameters.Length;
188                    var targetParameterIndex = currentIndex;
189
190                    // Make sure that the index is valid
191                    if (targetParameterIndex < 0 ||
192                        targetParameterIndex >= parameterCount)
193                        return false;
194
195                    // The parameter type should match the
196                    // given argument type
197                    var parameterType =
198                        parameters[targetParameterIndex].ParameterType;
199                    return parameterType.IsAssignableFrom(currentArgumentType);
200                };
201
202                // Make sure that the finder prefers exact
203                // type matches over compatible types
204                fuzzyList.AddCriteria(hasExactArgumentType, CriteriaType.Optional);
205            }
206        }
207    }
208}