Files
Umbraco-CMS/umbraco.MacroEngines.Juno/RazorDynamicNode/ExtensionMethodFinder.cs

183 lines
7.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Web.Compilation;
using System.Runtime.CompilerServices;
using System.Collections;
using System.Linq.Expressions;
namespace umbraco.MacroEngines
{
public static class ExtensionMethodFinder
{
private static List<MethodInfo> GetAllExtensionMethods(Type thisType, string name, int argumentCount, bool argsContainsThis)
{
//get extension methods from runtime
var candidates = (
from assembly in BuildManager.GetReferencedAssemblies().Cast<Assembly>()
where assembly.IsDefined(typeof(ExtensionAttribute), false)
from type in assembly.GetTypes()
where (type.IsDefined(typeof(ExtensionAttribute), false)
&& type.IsSealed && !type.IsGenericType && !type.IsNested)
from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
// this filters extension methods
where method.IsDefined(typeof(ExtensionAttribute), false)
select method
);
//search an explicit type (e.g. Enumerable, where most of the Linq methods are defined)
//if (explicitTypeToSearch != null)
//{
candidates = candidates.Concat(typeof(IEnumerable).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
//}
//filter by name
var methodsByName = candidates.Where(m => m.Name == name);
var isGenericAndRightParamCount = methodsByName.Where(m => m.GetParameters().Length == argumentCount + (argsContainsThis ? 0 : 1));
//find the right overload that can take genericParameterType
//which will be either DynamicNodeList or List<DynamicNode> which is IEnumerable`
var withGenericParameterType = isGenericAndRightParamCount.Select(m => new { m, t = firstParameterType(m) });
var methodsWhereArgZeroIsTargetType = (from method in withGenericParameterType
where
method.t != null && methodArgZeroHasCorrectTargetType(method.m, method.t, thisType)
select method);
return methodsWhereArgZeroIsTargetType.Select(mt => mt.m).ToList();
}
private static bool methodArgZeroHasCorrectTargetType(MethodInfo method, Type firstArgumentType, Type thisType)
{
//This is done with seperate method calls because you can't debug/watch lamdas - if you're trying to figure
//out why the wrong method is returned, it helps to be able to see each boolean result
return
// is it defined on me?
methodArgZeroHasCorrectTargetType_TypeMatchesExactly(method, firstArgumentType, thisType) ||
// or on any of my interfaces?
methodArgZeroHasCorrectTargetType_AnInterfaceMatches(method, firstArgumentType, thisType) ||
// or on any of my base types?
methodArgZeroHasCorrectTargetType_IsASubclassOf(method, firstArgumentType, thisType) ||
//share a common interface (e.g. IEnumerable)
methodArgZeroHasCorrectTargetType_ShareACommonInterface(method, firstArgumentType, thisType);
}
private static bool methodArgZeroHasCorrectTargetType_ShareACommonInterface(MethodInfo method, Type firstArgumentType, Type thisType)
{
Type[] interfaces = firstArgumentType.GetInterfaces();
if (interfaces.Length == 0)
{
return false;
}
bool result = interfaces.All(i => thisType.GetInterfaces().Contains(i));
return result;
}
private static bool methodArgZeroHasCorrectTargetType_IsASubclassOf(MethodInfo method, Type firstArgumentType, Type thisType)
{
bool result = thisType.IsSubclassOf(firstArgumentType);
return result;
}
private static bool methodArgZeroHasCorrectTargetType_AnInterfaceMatches(MethodInfo method, Type firstArgumentType, Type thisType)
{
bool result = thisType.GetInterfaces().Contains(firstArgumentType);
return result;
}
private static bool methodArgZeroHasCorrectTargetType_TypeMatchesExactly(MethodInfo method, Type firstArgumentType, Type thisType)
{
bool result = (thisType == firstArgumentType);
return result;
}
private static Type firstParameterType(MethodInfo m)
{
ParameterInfo[] p = m.GetParameters();
if (p.Count() > 0)
{
return p.First().ParameterType;
}
return null;
}
public static MethodInfo FindExtensionMethod(Type thisType, object[] args, string name, bool argsContainsThis)
{
Type genericType = null;
if (thisType.IsGenericType)
{
genericType = thisType.GetGenericArguments()[0];
}
var methods = GetAllExtensionMethods(thisType, name, args.Length, argsContainsThis);
if (methods.Count == 0)
{
return null;
}
MethodInfo methodToExecute = null;
if (methods.Count > 1)
{
//Given the args, lets get the types and compare the type sequence to try and find the correct overload
var argTypes = args.ToList().ConvertAll(o =>
{
Expression oe = (o as Expression);
if (oe != null)
{
return oe.Type.FullName;
}
return o.GetType().FullName;
});
var methodsWithArgTypes = methods.ConvertAll(method => new { method = method, types = method.GetParameters().ToList().ConvertAll(pi => pi.ParameterType.FullName) });
var firstMatchingOverload = methodsWithArgTypes.FirstOrDefault(m =>
{
return m.types.SequenceEqual(argTypes);
});
if (firstMatchingOverload != null)
{
methodToExecute = firstMatchingOverload.method;
}
}
if (methodToExecute == null)
{
MethodInfo firstMethod = methods.FirstOrDefault();
// NH: this is to ensure that it's always the correct one being chosen when using the LINQ extension methods
if (methods.Count > 1)
{
var firstGenericMethod = methods.FirstOrDefault(x => x.IsGenericMethodDefinition);
if (firstGenericMethod != null)
{
firstMethod = firstGenericMethod;
}
}
if (firstMethod != null)
{
if (firstMethod.IsGenericMethodDefinition)
{
if (genericType != null)
{
methodToExecute = firstMethod.MakeGenericMethod(genericType);
}
}
else
{
methodToExecute = firstMethod;
}
}
}
return methodToExecute;
}
}
}