Example #1
0
        public void Intercept(IInvocation invocation)
        {
            if (invocation.Method == GetGooseSourceMethod)
            {
                invocation.ReturnValue = _source;
                return;
            }

            if (_source == null)
            {
                throw new NullReferenceException($"{nameof(invocation.Method)}: Object reference not set to an instance of an object.");
            }

            var sourceType = _source.GetType();

            if (_blacklist.Contains(invocation.Method))
            {
                throw new GooseNotImplementedException(sourceType, invocation.Method);
            }

            if (_knownHandlers.ContainsKey(invocation.Method))
            {
                invocation.ReturnValue = _knownHandlers[invocation.Method](invocation.Arguments);
                return;
            }

            var sourceCandidateMethods = sourceType
                                         .GetMethods(BindingFlags.Public | BindingFlags.Instance)
                                         .Where(m => m.Name.Equals(invocation.Method.Name));

            var invocationParameters = invocation.Method.GetParameters();
            var candidates           = new List <MethodCompatibility>();

            foreach (var sourceMethod in sourceCandidateMethods)
            {
                var sourceParameters = sourceMethod.GetParameters();
                if (sourceParameters.Length != invocationParameters.Length)
                {
                    continue;
                }

                var argumentCompatibilities = new TypeCompatibility[invocation.Arguments.Length];
                var parameterCompatible     = true;
                for (var i = 0; i < sourceParameters.Length; i++)
                {
                    argumentCompatibilities[i] = GetCompatibility(invocationParameters[i].ParameterType, sourceParameters[i].ParameterType);
                    if (argumentCompatibilities[i] == TypeCompatibility.Incompatible)
                    {
                        parameterCompatible = false;
                        break;
                    }
                }

                if (!parameterCompatible)
                {
                    continue;
                }

                var returnTypeCompatibility = GetCompatibility(sourceMethod.ReturnType, invocation.Method.ReturnType);
                if (returnTypeCompatibility == TypeCompatibility.Incompatible)
                {
                    continue;
                }

                candidates.Add(new MethodCompatibility(sourceMethod, argumentCompatibilities, returnTypeCompatibility));
            }

            MethodCompatibility candidate = null;

            if (candidates.Count > 1)
            {
                var highestScoredCandidates = candidates
                                              .OrderByDescending(c => c.Score)
                                              .TakeWhile((c, i) => i == 0 || candidates[i].Score == candidates[i - 1].Score)
                                              .ToList();

                if (highestScoredCandidates.Count == 1)
                {
                    candidate = highestScoredCandidates.Single();
                }
                else
                {
                    var orderByDeclaringTypes = highestScoredCandidates
                                                .Select(c => new { Candidate = c, Inheritance = GetInheritanceString(c.Method.DeclaringType) })
                                                .OrderByDescending(x => x.Inheritance)
                                                .ToList();

                    if (orderByDeclaringTypes[0].Inheritance.Equals(orderByDeclaringTypes[1].Inheritance))
                    {
                        throw new GooseAmbiguousMatchException(orderByDeclaringTypes[0].Candidate.Method, orderByDeclaringTypes[1].Candidate.Method);
                    }

                    candidate = orderByDeclaringTypes[0].Candidate;
                }
            }
            else if (candidates.Count == 1)
            {
                candidate = candidates.Single();
            }
            else if (candidates.Count == 0)
            {
                _blacklist.Add(invocation.Method);
                throw new GooseNotImplementedException(sourceType, invocation.Method);
            }

            var handler = MakeHandler(invocation, candidate);

            invocation.ReturnValue = handler(invocation.Arguments);
            _knownHandlers.Add(invocation.Method, handler);
        }
Example #2
0
        private Func <object[], object> MakeHandler(IInvocation invocation, MethodCompatibility candidate)
        {
            var parameter    = Expression.Parameter(typeof(object[]));
            var variables    = new List <ParameterExpression>();
            var beforeInvoke = new List <Expression>();
            var invoke       = new List <Expression>();
            var afterInvoke  = new List <Expression>();

            var arguments        = new List <Expression>();
            var methodParameters = candidate.Method.GetParameters();

            for (var i = 0; i < candidate.ArgumentCompatibilities.Length; i++)
            {
                var        arrayIndex = Expression.ArrayIndex(parameter, Expression.Constant(i));
                Expression argument   = null;
                if (candidate.ArgumentCompatibilities[i] == TypeCompatibility.Same)
                {
                    argument = arrayIndex;
                }
                else if (candidate.ArgumentCompatibilities[i] == TypeCompatibility.FromGoose)
                {
                    var asIGooseTarget = Expression.Convert(arrayIndex, typeof(IGooseTyped));
                    var getSource      = Expression.Property(asIGooseTarget, nameof(IGooseTyped.Source));
                    argument = getSource;
                }
                else if (candidate.ArgumentCompatibilities[i] == TypeCompatibility.ToGoose)
                {
                    var gooseType          = Expression.Constant(methodParameters[i].ParameterType);
                    var callGooseExtension = Expression.Call(GooseExtensionMethod, arrayIndex, gooseType, Expression.Constant(_options.KnownTypes.ToArray()));
                    argument = callGooseExtension;
                }

                if (methodParameters[i].IsOut)
                {
                    var local = Expression.Variable(methodParameters[i].ParameterType.GetElementType());
                    variables.Add(local);
                    var assignLocal = Expression.Assign(local, Expression.Convert(argument, methodParameters[i].ParameterType.GetElementType()));
                    beforeInvoke.Add(assignLocal);

                    var arrayAccess = Expression.ArrayAccess(parameter, Expression.Constant(i));
                    var assignBack  = Expression.Assign(arrayAccess, Expression.Convert(local, typeof(object)));
                    afterInvoke.Add(assignBack);
                    arguments.Add(local);
                }
                else
                {
                    arguments.Add(Expression.Convert(argument, methodParameters[i].ParameterType));
                }
            }

            LabelTarget         returnTarget   = Expression.Label(typeof(object));
            ParameterExpression returnVariable = Expression.Variable(typeof(object));

            variables.Add(returnVariable);
            LabelExpression returnLabel = Expression.Label(returnTarget, returnVariable);

            afterInvoke.Add(Expression.Return(returnTarget, returnVariable));
            afterInvoke.Add(returnLabel);

            Expression invokeSource = Expression.Call(Expression.Constant(_source), candidate.Method, arguments);

            if (candidate.ReturnCompatibility == TypeCompatibility.FromGoose)
            {
                var asIGooseTarget = Expression.Convert(invokeSource, typeof(IGooseTyped));
                var getSource      = Expression.Property(asIGooseTarget, nameof(IGooseTyped.Source));
                invokeSource = getSource;
            }
            else if (candidate.ReturnCompatibility == TypeCompatibility.ToGoose)
            {
                var gooseType = Expression.Constant(invocation.Method.ReturnType);
                invokeSource = Expression.Call(GooseExtensionMethod, invokeSource, gooseType, Expression.Constant(_options.KnownTypes.ToArray()));
            }

            invoke.Add(returnVariable);
            if (candidate.Method.ReturnType == typeof(void))
            {
                invoke.Add(invokeSource);
                invoke.Add(Expression.Assign(returnVariable, Expression.Constant(null)));
            }
            else
            {
                invoke.Add(Expression.Assign(returnVariable, Expression.Convert(invokeSource, typeof(object))));
            }


            Expression body = Expression.Block(variables, beforeInvoke.Concat(invoke).Concat(afterInvoke));

            var exceptions = _options.KnownTypes
                             .Where(t => typeof(Exception).IsAssignableFrom(t.SourceType))
                             .ToList();

            if (exceptions.Count > 0)
            {
                var catches = exceptions
                              .OrderByDescending(e => GetInheritanceString(e.SourceType))
                              .Select(e =>
                {
                    var p                  = Expression.Parameter(e.SourceType);
                    var gooseType          = Expression.Constant(e.TargetType);
                    var callGooseExtension = Expression.Call(GooseExtensionMethod, p, gooseType, Expression.Constant(_options.KnownTypes.ToArray()));

                    var ctor      = typeof(WrappedException <>).MakeGenericType(e.TargetType).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).Single();
                    var throwWrap = Expression.Throw(Expression.New(ctor, Expression.Convert(callGooseExtension, e.TargetType)));
                    return(Expression.Catch(p, Expression.Block(throwWrap, Expression.Constant(null))));
                });

                body = Expression.TryCatch(body, catches.ToArray());
            }

            var lambda = Expression.Lambda <Func <object[], object> >(body, parameter);

            return(lambda.Compile());
        }