From cbe94ae6aa3c069b6e7b9b857db661495bf38d9a Mon Sep 17 00:00:00 2001 From: "agrath@gmail.com" Date: Sat, 26 Feb 2011 16:15:45 -1300 Subject: [PATCH] Refactored ExtensionMethod search code from DynamicNodeList (searches for extension methods e.g. .Random()) to tidy it up Moved Extension Method searching code to seperate static class Moved all the defined extension methods for DynamicNode into the same ExtensionMethods.cs file Changed the way that .ContainsAny within a where is evaluated to use the ExtensionMethodFinder class - should allow extra search methods to be defined --- .../RazorDynamicNode/DynamicNodeList.cs | 172 ++++-------------- .../DynamicNodeListExtensionMethods.cs | 30 --- .../DynamicNodeWhereHelpers.cs | 59 ------ .../RazorDynamicNode/DynamicQueryable.cs | 20 +- .../RazorDynamicNode/ExtensionMethodFinder.cs | 147 +++++++++++++++ .../RazorDynamicNode/ExtensionMethods.cs | 70 +++++++ .../umbraco.MacroEngines.csproj | 3 +- 7 files changed, 261 insertions(+), 240 deletions(-) delete mode 100644 umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicNodeListExtensionMethods.cs delete mode 100644 umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicNodeWhereHelpers.cs create mode 100644 umbraco.MacroEngines.Juno/RazorDynamicNode/ExtensionMethodFinder.cs diff --git a/umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicNodeList.cs b/umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicNodeList.cs index 7ccf1366da..a8f6ed941b 100644 --- a/umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicNodeList.cs +++ b/umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicNodeList.cs @@ -111,153 +111,47 @@ namespace umbraco.MacroEngines } } - List GetAllExtensionMethods(Type[] genericParameterTypeList, Type explicitTypeToSearch, string name, int argumentCount) - { - //get extension methods from runtime - var candidates = ( - from assembly in BuildManager.GetReferencedAssemblies().Cast() - 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(explicitTypeToSearch.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 + 1); - - //find the right overload that can take genericParameterType - //which will be either DynamicNodeList or List 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, genericParameterTypeList) - select method); - - return methodsWhereArgZeroIsTargetType.Select(mt => mt.m).ToList(); - } - private bool methodArgZeroHasCorrectTargetType(MethodInfo method, Type firstArgumentType, Type[] genericParameterTypeList) - { - //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, genericParameterTypeList) || - - // or on any of my interfaces? - methodArgZeroHasCorrectTargetType_AnInterfaceMatches(method, firstArgumentType, genericParameterTypeList) || - - // or on any of my base types? - methodArgZeroHasCorrectTargetType_IsASubclassOf(method, firstArgumentType, genericParameterTypeList) || - - //share a common interface (e.g. IEnumerable) - methodArgZeroHasCorrectTargetType_ShareACommonInterface(method, firstArgumentType, genericParameterTypeList); - - - } - - private static bool methodArgZeroHasCorrectTargetType_ShareACommonInterface(MethodInfo method, Type firstArgumentType, Type[] genericParameterTypeList) - { - Type[] interfaces = firstArgumentType.GetInterfaces(); - if (interfaces.Length == 0) - { - return false; - } - bool result = interfaces.All(i => genericParameterTypeList.Any(gt => gt.GetInterfaces().Contains(i))); - return result; - } - - private static bool methodArgZeroHasCorrectTargetType_IsASubclassOf(MethodInfo method, Type firstArgumentType, Type[] genericParameterTypeList) - { - bool result = genericParameterTypeList.Any(gt => gt.IsSubclassOf(firstArgumentType)); - return result; - } - - private static bool methodArgZeroHasCorrectTargetType_AnInterfaceMatches(MethodInfo method, Type firstArgumentType, Type[] genericParameterTypeList) - { - bool result = genericParameterTypeList.Any(gt => gt.GetInterfaces().Contains(firstArgumentType)); - return result; - } - - private static bool methodArgZeroHasCorrectTargetType_TypeMatchesExactly(MethodInfo method, Type firstArgumentType, Type[] genericParameterTypeList) - { - bool result = genericParameterTypeList.Any(gt => gt == firstArgumentType); - return result; - } - private Type firstParameterType(MethodInfo m) - { - ParameterInfo[] p = m.GetParameters(); - if (p.Count() > 0) - { - return p.First().ParameterType; - } - return null; - } private object ExecuteExtensionMethod(object[] args, string name) { - object result; - //Extension method - Type tObject = Items.GetType(); - Type t = tObject.GetGenericArguments()[0]; + object result = null; - var methods = GetAllExtensionMethods(new Type[] { typeof(DynamicNodeList), tObject }, typeof(Enumerable), name, args.Length); - - if (methods.Count == 0) + MethodInfo methodToExecute = ExtensionMethodFinder.FindExtensionMethod(typeof(IEnumerable), args, name); + if (methodToExecute == null) + { + methodToExecute = ExtensionMethodFinder.FindExtensionMethod(typeof(DynamicNodeList), args, name); + } + if (methodToExecute != null) + { + if (methodToExecute.GetParameters().First().ParameterType == typeof(DynamicNodeList)) + { + var genericArgs = (new[] { this }).Concat(args); + result = methodToExecute.Invoke(null, genericArgs.ToArray()); + } + else + { + var genericArgs = (new[] { Items }).Concat(args); + result = methodToExecute.Invoke(null, genericArgs.ToArray()); + } + } + else { throw new MissingMethodException(); } - - MethodInfo firstMethod = methods.First(); - // NH: this is to ensure that it's always the correct one being chosen when using the LINQ extension methods - if (methods.Count > 1) - firstMethod = methods.First(x => x.IsGenericMethodDefinition); - - MethodInfo methodToExecute = null; - if (firstMethod.IsGenericMethodDefinition) + if (result != null) { - methodToExecute = firstMethod.MakeGenericMethod(t); - } - else - { - methodToExecute = firstMethod; - } - if (methodToExecute.GetParameters().First().ParameterType == typeof(DynamicNodeList)) - { - var genericArgs = (new[] { this }).Concat(args); - result = methodToExecute.Invoke(null, genericArgs.ToArray()); - } - else - { - var genericArgs = (new[] { Items }).Concat(args); - result = methodToExecute.Invoke(null, genericArgs.ToArray()); - } - - if (result is IEnumerable) - { - result = new DynamicNodeList((IEnumerable)result); - } - if (result is IEnumerable) - { - result = new DynamicNodeList((IEnumerable)result); - } - if (result is INode) - { - result = new DynamicNode((INode)result); + if (result is IEnumerable) + { + result = new DynamicNodeList((IEnumerable)result); + } + if (result is IEnumerable) + { + result = new DynamicNodeList((IEnumerable)result); + } + if (result is INode) + { + result = new DynamicNode((INode)result); + } } return result; } diff --git a/umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicNodeListExtensionMethods.cs b/umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicNodeListExtensionMethods.cs deleted file mode 100644 index f25427b83c..0000000000 --- a/umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicNodeListExtensionMethods.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace umbraco.MacroEngines -{ - public static class DynamicNodeListExtensionMethods - { - public static DynamicNodeList Random(this DynamicNodeList all, int Min, int Max) - { - //get a random number generator - Random r = new Random(); - //choose the number of elements to be returned between Min and Max - int Number = r.Next(Min, Max); - //Call the other method - return Random(all, Number); - } - public static DynamicNodeList Random(this DynamicNodeList all, int Max) - { - //Randomly order the items in the set by a Guid, Take the correct number, and return this wrapped in a new DynamicNodeList - return new DynamicNodeList(all.Items.OrderBy(x => Guid.NewGuid()).Take(Max)); - } - - public static DynamicNode Random(this DynamicNodeList all) - { - return all.Items.OrderBy(x => Guid.NewGuid()).First(); - } - } -} \ No newline at end of file diff --git a/umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicNodeWhereHelpers.cs b/umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicNodeWhereHelpers.cs deleted file mode 100644 index 5432711f22..0000000000 --- a/umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicNodeWhereHelpers.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace umbraco.MacroEngines -{ - public static class DynamicNodeWhereHelpers - { - public static bool ContainsAny(string haystack, List needles) - { - if (!string.IsNullOrEmpty(haystack) || needles.Count > 0) - { - foreach (string value in needles) - { - if (haystack.Contains(value)) - return true; - } - } - return false; - } - public static bool ContainsAny(string haystack, params string[] needles) - { - if (!string.IsNullOrEmpty(haystack) || needles.Length > 0) - { - foreach (string value in needles) - { - if (haystack.Contains(value)) - return true; - } - } - return false; - } - public static bool ContainsAny(string haystack, StringComparison comparison, List needles) - { - if (!string.IsNullOrEmpty(haystack) || needles.Count > 0) - { - foreach (string value in needles) - { - if (haystack.IndexOf(value, comparison) >= 0) - return true; - } - } - return false; - } - public static bool ContainsAny(string haystack, StringComparison comparison, params string[] needles) - { - if (!string.IsNullOrEmpty(haystack) || needles.Length > 0) - { - foreach (string value in needles) - { - if (haystack.IndexOf(value, comparison) >= 0) - return true; - } - } - return false; - } - } -} diff --git a/umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicQueryable.cs b/umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicQueryable.cs index afc9044932..6d8202c3b5 100644 --- a/umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicQueryable.cs +++ b/umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicQueryable.cs @@ -1409,19 +1409,19 @@ namespace System.Linq.Dynamic } } } - if (id == "ContainsAny" && type == typeof(string) && instanceAsString != null) - { - //stringProperty.ContainsAny(List) - Expression[] newArgs = (new List() { Expression.Invoke(instanceAsString, instanceExpression) }).Concat(args).ToArray(); - int findMethodResult = FindMethod(typeof(DynamicNodeWhereHelpers), id, true, newArgs, out mb); - if (findMethodResult == 1) - { - return CallMethodOnDynamicNode(instance, newArgs, instanceAsString, instanceExpression, (MethodInfo)mb, true); - } - } switch (FindMethod(type, id, instance == null, args, out mb)) { case 0: + //not found + if (type == typeof(string) && instanceAsString != null) + { + Expression[] newArgs = (new List() { Expression.Invoke(instanceAsString, instanceExpression) }).Concat(args).ToArray(); + mb = ExtensionMethodFinder.FindExtensionMethod(typeof(string), newArgs, id); + if (mb != null) + { + return CallMethodOnDynamicNode(instance, newArgs, instanceAsString, instanceExpression, (MethodInfo)mb, true); + } + } throw ParseError(errorPos, Res.NoApplicableMethod, id, GetTypeName(type)); case 1: diff --git a/umbraco.MacroEngines.Juno/RazorDynamicNode/ExtensionMethodFinder.cs b/umbraco.MacroEngines.Juno/RazorDynamicNode/ExtensionMethodFinder.cs new file mode 100644 index 0000000000..d1c33f9295 --- /dev/null +++ b/umbraco.MacroEngines.Juno/RazorDynamicNode/ExtensionMethodFinder.cs @@ -0,0 +1,147 @@ +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; + +namespace umbraco.MacroEngines +{ + public static class ExtensionMethodFinder + { + private static List GetAllExtensionMethods(Type thisType, string name, int argumentCount) + { + //get extension methods from runtime + var candidates = ( + from assembly in BuildManager.GetReferencedAssemblies().Cast() + 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 + 1); + + //find the right overload that can take genericParameterType + //which will be either DynamicNodeList or List 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) + { + Type genericType = null; + if (thisType.IsGenericType) + { + genericType = thisType.GetGenericArguments()[0]; + } + + var methods = GetAllExtensionMethods(thisType, name, args.Length); + + if (methods.Count == 0) + { + return null; + } + + MethodInfo firstMethod = methods.First(); + // NH: this is to ensure that it's always the correct one being chosen when using the LINQ extension methods + if (methods.Count > 1) + firstMethod = methods.First(x => x.IsGenericMethodDefinition); + + MethodInfo methodToExecute = null; + if (firstMethod.IsGenericMethodDefinition) + { + if (genericType != null) + { + methodToExecute = firstMethod.MakeGenericMethod(genericType); + } + } + else + { + methodToExecute = firstMethod; + } + return methodToExecute; + } + } +} diff --git a/umbraco.MacroEngines.Juno/RazorDynamicNode/ExtensionMethods.cs b/umbraco.MacroEngines.Juno/RazorDynamicNode/ExtensionMethods.cs index 19b99ccc63..33fe98f662 100644 --- a/umbraco.MacroEngines.Juno/RazorDynamicNode/ExtensionMethods.cs +++ b/umbraco.MacroEngines.Juno/RazorDynamicNode/ExtensionMethods.cs @@ -22,5 +22,75 @@ namespace umbraco.MacroEngines } return flattenedList; } + + + public static DynamicNodeList Random(this DynamicNodeList all, int Min, int Max) + { + //get a random number generator + Random r = new Random(); + //choose the number of elements to be returned between Min and Max + int Number = r.Next(Min, Max); + //Call the other method + return Random(all, Number); + } + public static DynamicNodeList Random(this DynamicNodeList all, int Max) + { + //Randomly order the items in the set by a Guid, Take the correct number, and return this wrapped in a new DynamicNodeList + return new DynamicNodeList(all.Items.OrderBy(x => Guid.NewGuid()).Take(Max)); + } + + public static DynamicNode Random(this DynamicNodeList all) + { + return all.Items.OrderBy(x => Guid.NewGuid()).First(); + } + + public static bool ContainsAny(this string haystack, List needles) + { + if (!string.IsNullOrEmpty(haystack) || needles.Count > 0) + { + foreach (string value in needles) + { + if (haystack.Contains(value)) + return true; + } + } + return false; + } + public static bool ContainsAny(this string haystack, params string[] needles) + { + if (!string.IsNullOrEmpty(haystack) || needles.Length > 0) + { + foreach (string value in needles) + { + if (haystack.Contains(value)) + return true; + } + } + return false; + } + public static bool ContainsAny(this string haystack, StringComparison comparison, List needles) + { + if (!string.IsNullOrEmpty(haystack) || needles.Count > 0) + { + foreach (string value in needles) + { + if (haystack.IndexOf(value, comparison) >= 0) + return true; + } + } + return false; + } + public static bool ContainsAny(this string haystack, StringComparison comparison, params string[] needles) + { + if (!string.IsNullOrEmpty(haystack) || needles.Length > 0) + { + foreach (string value in needles) + { + if (haystack.IndexOf(value, comparison) >= 0) + return true; + } + } + return false; + } } } diff --git a/umbraco.MacroEngines.Juno/umbraco.MacroEngines.csproj b/umbraco.MacroEngines.Juno/umbraco.MacroEngines.csproj index e1822382c3..cd36be37dc 100644 --- a/umbraco.MacroEngines.Juno/umbraco.MacroEngines.csproj +++ b/umbraco.MacroEngines.Juno/umbraco.MacroEngines.csproj @@ -67,16 +67,15 @@ - + -