using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Linq.Expressions;
using System.Web.Services.Description;
using Umbraco.Core.Cache;
namespace Umbraco.Core.Dynamics
{
///
/// Utility class for finding extension methods on a type to execute
///
internal static class ExtensionMethodFinder
{
///
/// The static cache for extension methods found that match the criteria that we are looking for
///
private static readonly ConcurrentDictionary, MethodInfo[]> MethodCache = new ConcurrentDictionary, MethodInfo[]>();
///
/// Returns the enumerable of all extension method info's in the app domain = USE SPARINGLY!!!
///
///
///
/// We cache this as a sliding 5 minute exiration, in unit tests there's over 1100 methods found, surely that will eat up a bit of memory so we want
/// to make sure we give it back.
///
private static IEnumerable GetAllExtensionMethodsInAppDomain(IRuntimeCacheProvider runtimeCacheProvider)
{
if (runtimeCacheProvider == null) throw new ArgumentNullException("runtimeCacheProvider");
return runtimeCacheProvider.GetCacheItem(typeof (ExtensionMethodFinder).Name, () => TypeFinder.GetAssembliesWithKnownExclusions()
// assemblies that contain extension methods
.Where(a => a.IsDefined(typeof (ExtensionAttribute), false))
// types that contain extension methods
.SelectMany(a => a.GetTypes()
.Where(t => t.IsDefined(typeof (ExtensionAttribute), false) && t.IsSealed && t.IsGenericType == false && t.IsNested == false))
// actual extension methods
.SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(m => m.IsDefined(typeof (ExtensionAttribute), false)))
// and also IEnumerable extension methods - because the assembly is excluded
.Concat(typeof (Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public))
//If we don't do this then we'll be scanning all assemblies each time!
.ToArray(),
//only cache for 5 minutes
timeout: TimeSpan.FromMinutes(5),
//each time this is accessed it will be for 5 minutes longer
isSliding:true);
}
///
/// Returns all extension methods found matching the definition
///
///
/// The runtime cache is used to temporarily cache all extension methods found in the app domain so that
/// while we search for individual extension methods, the process will be reasonably 'quick'. We then statically
/// cache the MethodInfo's that we are looking for and then the runtime cache will expire and give back all that memory.
///
///
///
///
/// The arguments EXCLUDING the 'this' argument in an extension method
///
///
///
/// NOTE: This will be an intensive method to call! Results will be cached based on the key (args) of this method
///
internal static IEnumerable GetAllExtensionMethods(IRuntimeCacheProvider runtimeCache, Type thisType, string name, int argumentCount)
{
var key = new Tuple(thisType, name, argumentCount);
return MethodCache.GetOrAdd(key, tuple =>
{
var candidates = GetAllExtensionMethodsInAppDomain(runtimeCache);
// filter by name
var filtr1 = candidates.Where(m => m.Name == name);
// filter by args count
// ensure we add + 1 to the arg count because the 'this' arg is not included in the count above!
var filtr2 = filtr1.Where(m => m.GetParameters().Length == argumentCount + 1);
// filter by first parameter type (target of the extension method)
// ie find the right overload that can take genericParameterType
// (which will be either DynamicNodeList or List which is IEnumerable)
var filtr3 = filtr2.Select(x =>
{
var t = x.GetParameters()[0].ParameterType; // exists because of +1 above
var bindings = new Dictionary();
if (TypeHelper.MatchType(thisType, t, bindings) == false) return null;
// create the generic method if necessary
if (x.ContainsGenericParameters == false) return x;
var targs = t.GetGenericArguments().Select(y => bindings[y.Name]).ToArray();
return x.MakeGenericMethod(targs);
}).Where(x => x != null);
return filtr3.ToArray();
});
}
private static MethodInfo DetermineMethodFromParams(IEnumerable methods, Type genericType, IEnumerable