Files
Umbraco-CMS/umbraco.MacroEngines.Juno/RazorDynamicNode/DynamicNodeList.cs
agrath@gmail.com 71c5e35443 Added DynamicNodeWalker and implementation on DynamicNode and DynamicNodeList
DynamicNodeWalker is our secret weapon in the fight against the Rebel XSLT Alliance
Navigate nodes by calling Up(), Down(), Next() and Previous() on them
Next(1) will jump two items along within the current list, whereas Next() will walk by one within the list
Previous(1) will move two items backwards within the current list
Up() is a special wrapper around .Parent which has an overload .Up(int) to replace @Model.Parent.Parent.Parent... [.Up(2)]
Down() will take you to the first Child item and is equivilent to .Children.First(), use .Down(1) to replace .Children.First().Children
If one of the NodeWalker functions fails to find a node at the requested position, it will return null
2011-02-24 17:19:50 -13:00

280 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Dynamic;
using umbraco.interfaces;
using System.Collections;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Web.Compilation;
using System.Linq.Expressions;
using System.Linq.Dynamic;
namespace umbraco.MacroEngines
{
public class DynamicNodeList : DynamicObject, IEnumerable
{
public IEnumerable<DynamicNode> Items { get; set; }
public DynamicNodeList()
{
Items = new List<DynamicNode>();
}
public DynamicNodeList(IEnumerable<DynamicNode> items)
{
List<DynamicNode> list = items.ToList();
list.ForEach(node => node.ownerList = this);
Items = list;
}
public DynamicNodeList(IEnumerable<INode> items)
{
List<DynamicNode> list = items.Select(x => new DynamicNode(x)).ToList();
list.ForEach(node => node.ownerList = this);
Items = list;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
var name = binder.Name;
if (name == "Where")
{
string predicate = args.First().ToString();
var values = args.Skip(1).ToArray();
result = new DynamicNodeList(this.Where<DynamicNode>(predicate, values).ToList());
return true;
}
if (name == "OrderBy")
{
result = new DynamicNodeList(this.OrderBy<DynamicNode>(args.First().ToString()).ToList());
return true;
}
try
{
//Property?
result = Items.GetType().InvokeMember(binder.Name,
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.GetProperty,
null,
Items,
args);
return true;
}
catch (MissingMethodException)
{
try
{
//Static or Instance Method?
result = Items.GetType().InvokeMember(binder.Name,
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Static |
System.Reflection.BindingFlags.InvokeMethod,
null,
Items,
args);
return true;
}
catch (MissingMethodException)
{
try
{
result = ExecuteExtensionMethod(args, name);
return true;
}
catch (TargetInvocationException)
{
//We do this to enable error checking of Razor Syntax when a method e.g. ElementAt(2) is used.
//When the Script is tested, there's no Children which means ElementAt(2) is invalid (IndexOutOfRange)
//Instead, we are going to return an empty DynamicNode.
result = new DynamicNode();
return true;
}
catch
{
result = null;
return false;
}
}
}
catch
{
result = null;
return false;
}
}
List<MethodInfo> GetAllExtensionMethods(Type[] genericParameterTypeList, Type explicitTypeToSearch, string name, int argumentCount)
{
//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(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<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, 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];
var methods = GetAllExtensionMethods(new Type[] { typeof(DynamicNodeList), tObject }, typeof(Enumerable), name, args.Length);
if (methods.Count == 0)
{
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)
{
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<INode>)
{
result = new DynamicNodeList((IEnumerable<INode>)result);
}
if (result is IEnumerable<DynamicNode>)
{
result = new DynamicNodeList((IEnumerable<DynamicNode>)result);
}
if (result is INode)
{
result = new DynamicNode((INode)result);
}
return result;
}
public IEnumerator GetEnumerator()
{
return Items.GetEnumerator();
}
public IQueryable<T> Where<T>(string predicate, params object[] values)
{
return ((IQueryable<T>)Items.AsQueryable()).Where(predicate, values);
}
public IQueryable<T> OrderBy<T>(string key)
{
return ((IQueryable<T>)Items.AsQueryable()).OrderBy(key);
}
}
}