Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prototyping reflection of external libraries #1132

Merged
merged 28 commits into from
Aug 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2004f56
initial prototype
Aug 8, 2019
9188e17
start migrating ui methods into reflection namespace
Aug 8, 2019
fe70cce
don't know how to deal with generic methods yet - return null for now
Aug 8, 2019
63ac9a9
clean tree
Aug 8, 2019
94a65b4
MakeStatic is now WIP
Aug 9, 2019
e1565ba
Move Create.MethodBase to the Reflection_Engine
Aug 9, 2019
ddbd5e6
Move Query.GenericTypeContraint to the Reflection_Engine
Aug 9, 2019
598a2b8
Move Query.ParameterWithConstraints to the Reflection_Engine
Aug 9, 2019
0222033
Remove serialisation of Delegates
Aug 9, 2019
d3b8316
Fix Deserialisation of MethodBase
Aug 9, 2019
7df300d
Fix association of components with back-end methods that return void
Aug 12, 2019
4936892
improve readability for parameter in Engine.Reflection.Create.MethodBase
Aug 13, 2019
4b55f13
Rename Compute.Compile to Create.Delegate
Aug 13, 2019
ee037bc
replace instances
Aug 13, 2019
3841f34
Rally all cross-cases of isntance method and methods returning void u…
Aug 13, 2019
5a16b10
Move method to collected methods in nested types from Numpy_Toolkit
Aug 13, 2019
cfbf766
files doesn't require this change
Aug 13, 2019
9fbf089
fix nestedmethods aggregation
Aug 13, 2019
099556c
make extraction of external methods more robust
Aug 13, 2019
0acb971
external methods should not be in the bhom method list
Aug 13, 2019
cef75d2
Store methods and constructors in one field - switch to MethodBase
Aug 16, 2019
b84f01d
Settle on class and method names
Aug 16, 2019
4c1ef99
rename MakeGeneric to MakeFromGeneric to be clearer
Aug 21, 2019
266b17c
fix MakeFromGeneric file name
Aug 21, 2019
a84af06
removing the Reflection.Modify.MakeStatic method
Aug 27, 2019
6671d60
pushing the csproj file
Aug 27, 2019
31c59fd
Remove gatherer methods from the list of external methods
Aug 27, 2019
54b498f
adding check for null namespace
Aug 27, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Data_Engine/Create/Tree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ public static Tree<T> Tree<T>(List<T> items, List<List<string>> paths, string na
return tree;
}



/***************************************************/
}
}
1 change: 1 addition & 0 deletions Data_Engine/Data_Engine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
<Compile Include="Create\GraphLink.cs" />
<Compile Include="Create\GraphNode.cs" />
<Compile Include="Modify\Add.cs" />
<Compile Include="Modify\GroupByName.cs" />
<Compile Include="Modify\ShortenBranches.cs" />
<Compile Include="Modify\Pop.cs" />
<Compile Include="Modify\RemoveLink.cs" />
Expand Down
80 changes: 80 additions & 0 deletions Data_Engine/Modify/GroupByName.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* This file is part of the Buildings and Habitats object Model (BHoM)
* Copyright (c) 2015 - 2018, the respective contributors. All rights reserved.
*
* Each contributor holds copyright over their respective contributions.
* The project versioning (Git) records all such contribution source information.
*
*
* The BHoM is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* The BHoM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this code. If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
*/

using BH.oM.Data.Collections;
using System.Collections.Generic;
using System.Linq;

namespace BH.Engine.Data
{
public static partial class Modify
{
/*************************************/
/**** Public Methods ****/
/*************************************/

public static Tree<T> GroupByName<T>(this Tree<T> tree)
Copy link
Member Author

@epignatelli epignatelli Aug 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method has been migrated from the UI: https://github.com/BHoM/BHoM_UI/blob/2ad2b8a3a7c1a27596a5c88097da34d28636e8f8/UI_Engine/Modify/GroupMethodsByName.cs#L39
Old:

public static Tree<MethodBase> GroupMethodsByName<MethodBase>(this Tree<MethodBase> tree)

New:

public static Tree<T> GroupByName<T>(this Tree<T> tree)

Note that the name MethodBase was actually used as the name of a generic parameter, and was not a real specialisation of a generic method.

{
if (tree.Children.Count > 0)
{
if (tree.Children.Values.Any(x => x.Value != null))
{
var groups = tree.Children.GroupBy(x =>
{
int index = x.Key.IndexOf('(');
if (index > 0)
return x.Key.Substring(0, index);
else
return x.Key;
});

if (groups.Count() > 1)
{
Dictionary<string, Tree<T>> children = new Dictionary<string, Tree<T>>();
foreach (var group in groups)
{
if (group.Count() == 1)
{
if (group.First().Value.Value == null)
children.Add(group.Key, group.First().Value);
else
children.Add(group.Key, new Tree<T> { Name = group.Key, Value = group.First().Value.Value });
}
else
children.Add(group.Key, new Tree<T> { Name = group.Key, Children = group.ToDictionary(x => x.Key, x => x.Value) });
}
tree.Children = children;
}
}
else
{
foreach (var child in tree.Children.Values)
GroupByName(child);
}
}

return tree;
}

/*************************************/
}
}
118 changes: 118 additions & 0 deletions Reflection_Engine/Convert/ToFunc.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* This file is part of the Buildings and Habitats object Model (BHoM)
* Copyright (c) 2015 - 2019, the respective contributors. All rights reserved.
*
* Each contributor holds copyright over their respective contributions.
* The project versioning (Git) records all such contribution source information.
*
*
* The BHoM is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* The BHoM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this code. If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
*/

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace BH.Engine.Reflection
{
public static partial class Convert
{
/***************************************************/
/**** Public Methods ****/
/***************************************************/


public static Func<object[], object> ToFunc(this MethodBase method)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This set of methods are the port of CompileFunction in the BHoM_UI: https://github.com/BHoM/BHoM_UI/blob/d065743b720aa171b442b91de4b07da27974509b/BHoM_UI/Templates/MethodCaller.cs#L144

It has now been refactored into a Convert method that returns a Func<object[], object>.
The convergence of any method to a Delegate that takes object[] as input and returns object is necessary to generalise the MethodCaller in the UI.

{
if (method is MethodInfo)
return ((MethodInfo)method).ToFunc();
else if (method is ConstructorInfo)
return ((ConstructorInfo)method).ToFunc();
return null;
}

/***************************************************/

public static Func<object[], object> ToFunc(this MethodInfo method)
{
ParameterExpression lambdaInput = Expression.Parameter(typeof(object[]), "x");
Expression[] inputs = method.GetParameters().Select((x, i) => Expression.Convert(Expression.ArrayIndex(lambdaInput, Expression.Constant(i)), x.ParameterType)).ToArray();

MethodCallExpression methodExpression;
if (method.IsStatic)
{
methodExpression = Expression.Call(method, inputs);
if (method.ReturnType == typeof(void))
return Expression.Lambda<Action<object[]>>(Expression.Convert(methodExpression, typeof(void)), lambdaInput).Compile().ToFunc();
else
return Expression.Lambda<Func<object[], object>>(Expression.Convert(methodExpression, typeof(object)), lambdaInput).Compile();
}
else
{
ParameterExpression instanceParameter = Expression.Parameter(typeof(object), "instance");
Expression instanceInput = Expression.Convert(instanceParameter, method.DeclaringType);
methodExpression = Expression.Call(instanceInput, method, inputs);

if (method.ReturnType == typeof(void))
{
return Expression.Lambda<Action<object, object[]>>(
Expression.Convert(methodExpression, typeof(void)),
new ParameterExpression[] { instanceParameter, lambdaInput }
).Compile().ToFunc();
}
else
{
return Expression.Lambda<Func<object, object[], object>>(
Expression.Convert(methodExpression, typeof(object)),
new ParameterExpression[] { instanceParameter, lambdaInput }
).Compile().ToFunc();
}
}
}

/***************************************************/

public static Func<object[], object> ToFunc(this ConstructorInfo ctor)
{
ParameterExpression lambdaInput = Expression.Parameter(typeof(object[]), "x");
Expression[] inputs = ctor.GetParameters().Select((x, i) => Expression.Convert(Expression.ArrayIndex(lambdaInput, Expression.Constant(i)), x.ParameterType)).ToArray();
NewExpression constructorExpression = Expression.New(ctor as ConstructorInfo, inputs);
return Expression.Lambda<Func<object[], object>>(Expression.Convert(constructorExpression, typeof(object)), lambdaInput).Compile();
}

/***************************************************/

public static Func<object[], object> ToFunc(this Action<object[]> act)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one solves BHoM/BHoM_UI#118

{
return inputs => { act(inputs); return true; };
}

/***************************************************/

public static Func<object[], object> ToFunc(this Func<object, object[], object> func)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one solves the convergence to the delegate above for instance methods

{
return inputs => { return func(inputs[0], inputs); };
}

/***************************************************/

public static Func<object[], object> ToFunc(this Action<object, object[]> act)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one solves BHoM/BHoM_UI#118 for instance methods

{
return inputs => { act(inputs[0], inputs); return true; };
}

/***************************************************/
}
}
2 changes: 1 addition & 1 deletion Reflection_Engine/Convert/ToText.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public static string ToText(this Type type, bool includePath = false, bool repla
{
Type[] types = type.GetGenericArguments();

if (replaceGeneric && types.Count() == 1 && !type.Namespace.StartsWith("BH"))
if (replaceGeneric && types.Count() == 1 && type.Namespace != null && !type.Namespace.StartsWith("BH"))
return types[0].ToText(includePath, replaceGeneric);
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static partial class Create
/**** Public Methods ****/
/*******************************************/

public static MethodBase MethodBase(Type type, string methodName, List<string> paramTypes)
public static MethodBase MethodBase(Type type, string methodName, List<string> paramTypeNames)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is necessary to solve the dependency between the Serialiser_Engine and the Reflection_Engine.

{
List<MethodBase> methods;
if (methodName == ".ctor")
Expand All @@ -48,12 +48,12 @@ public static MethodBase MethodBase(Type type, string methodName, List<string> p
if (method.Name == methodName)
{
ParameterInfo[] parameters = method.ParametersWithConstraints();
if (parameters.Length == paramTypes.Count)
if (parameters.Length == paramTypeNames.Count)
{
bool matching = true;
List<string> names = parameters.Select(x => Convert.ToJson(x.ParameterType)).ToList();
for (int i = 0; i < paramTypes.Count; i++)
matching &= names[i] == paramTypes[i];
List<string> names = parameters.Select(x => x.ParameterType.Name).ToList();
for (int i = 0; i < paramTypeNames.Count; i++)
matching &= names[i] == paramTypeNames[i];

if (matching)
{
Expand All @@ -66,5 +66,25 @@ public static MethodBase MethodBase(Type type, string methodName, List<string> p
}

/*******************************************/

public static MethodBase MethodBase(Type type, string methodName, List<Type> paramTypes)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method improves the retrieval of MethodBases by using the Type.GetMehod method rather than using Type.GetMethods and iterate on them.

Although, it fails on generic types, since we compile the methods on the constraints of the generic types, rather than on the generic types themselves.

It also raises #1138

{
MethodBase method;
if (methodName == ".ctor")
method = type.GetConstructor(paramTypes.ToArray());
else
method = type.GetMethod(methodName, paramTypes.ToArray());

// the above will return null if the type is a generic type
// that is because we serialise a generic method with its constraints
// rather than serialising the generics
if (method != null)
return method;

// So, let's try the other overload
return MethodBase(type, methodName, paramTypes.Select(x => x.Name).ToList());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uses the old overload in case the new fails when dealing with generic types (I believe around 10% of the cases, maybe less)

}

/*******************************************/
}
}
78 changes: 78 additions & 0 deletions Reflection_Engine/Create/MethodInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* This file is part of the Buildings and Habitats object Model (BHoM)
* Copyright (c) 2015 - 2018, the respective contributors. All rights reserved.
*
* Each contributor holds copyright over their respective contributions.
* The project versioning (Git) records all such contribution source information.
*
*
* The BHoM is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* The BHoM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this code. If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace BH.Engine.Reflection
{
public static partial class Create
{
/*************************************/
/**** Public Methods ****/
/*************************************/

public static MethodInfo MethodInfo(this Type declaringType, string methodName, List<Type> paramTypes)
Copy link
Member Author

@epignatelli epignatelli Aug 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migration from the BHoM_UI

{
MethodInfo foundMethod = null;
List<MethodInfo> methods = declaringType.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly).ToList();

for (int k = 0; k < methods.Count; k++)
{
MethodInfo method = methods[k];

if (method.Name == methodName)
{
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length == paramTypes.Count)
{
if (method.ContainsGenericParameters)
{
Type[] generics = method.GetGenericArguments().Select(x => x.MakeFromGeneric()).ToArray();
method = method.MakeGenericMethod(generics);
parameters = method.GetParameters();
}

bool matching = true;
for (int i = 0; i < paramTypes.Count; i++)
{
matching &= (paramTypes[i] == null || parameters[i].ParameterType == paramTypes[i]);
}
if (matching)
{
foundMethod = method;
break;
}
}
}
}

return foundMethod;
}

/*************************************/
}
}
Loading