Repository: tinkerpop Updated Branches: refs/heads/TINKERPOP-1827 [created] f0120cbb9
Invoke traversal methods with generic parameters Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/74a4f8af Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/74a4f8af Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/74a4f8af Branch: refs/heads/TINKERPOP-1827 Commit: 74a4f8af2f0e155f0b436dfc44a1e7eef794b1c6 Parents: b1f98fc Author: Jorge Bay Gondra <jorgebaygon...@gmail.com> Authored: Fri Oct 27 10:55:20 2017 +0200 Committer: Jorge Bay Gondra <jorgebaygon...@gmail.com> Committed: Tue Nov 21 11:46:17 2017 +0100 ---------------------------------------------------------------------- .../TraversalEvaluation/ITokenParameter.cs | 13 ++- .../TraversalEvaluation/NumericParameter.cs | 10 +++ .../StaticTraversalParameter.cs | 11 +++ .../TraversalEvaluation/StringParameter.cs | 10 +++ .../Gherkin/TraversalEvaluation/Token.cs | 15 ++-- .../TraversalEvaluationTests.cs | 23 ++++-- .../TraversalEvaluation/TraversalParser.cs | 86 ++++++++++++++++---- .../TraversalPredicateParameter.cs | 11 +++ .../TraversalTokenParameter.cs | 11 +++ .../Gremlin.Net.IntegrationTest.csproj | 6 +- 10 files changed, 164 insertions(+), 32 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/74a4f8af/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/ITokenParameter.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/ITokenParameter.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/ITokenParameter.cs index 1c940db..5c8197f 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/ITokenParameter.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/ITokenParameter.cs @@ -21,10 +21,21 @@ #endregion +using System; + namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation { public interface ITokenParameter { - + /// <summary> + /// Gets the value of the parameter + /// </summary> + object GetValue(); + + /// <summary> + /// Gets the type of the parameter + /// </summary> + /// <returns></returns> + Type GetParameterType(); } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/74a4f8af/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/NumericParameter.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/NumericParameter.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/NumericParameter.cs index 9effc17..378680c 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/NumericParameter.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/NumericParameter.cs @@ -56,6 +56,16 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation { return $"NumericParameter<{typeof(T).Name}>({Value})"; } + + public object GetValue() + { + return Value; + } + + public Type GetParameterType() + { + return typeof(T); + } } internal static class NumericParameter http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/74a4f8af/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/StaticTraversalParameter.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/StaticTraversalParameter.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/StaticTraversalParameter.cs index b054cbc..8e0fbf9 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/StaticTraversalParameter.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/StaticTraversalParameter.cs @@ -24,6 +24,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Gremlin.Net.Process.Traversal; namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation { @@ -47,6 +48,16 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation return Parts != null ? Parts.GetHashCode() : 0; } + public object GetValue() + { + throw new NotImplementedException(); + } + + public Type GetParameterType() + { + return typeof(ITraversal); + } + public IList<Token> Parts { get; } public StaticTraversalParameter(IList<Token> parts) http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/74a4f8af/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/StringParameter.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/StringParameter.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/StringParameter.cs index ad182b5..82f6cd9 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/StringParameter.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/StringParameter.cs @@ -65,5 +65,15 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation { return $"{GetType().Name}({Value})"; } + + public object GetValue() + { + return Value; + } + + public Type GetParameterType() + { + return typeof(string); + } } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/74a4f8af/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/Token.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/Token.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/Token.cs index 633e2b5..539be89 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/Token.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/Token.cs @@ -23,13 +23,15 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation { internal class Token : IEquatable<Token> { - private static readonly IList<ITokenParameter> EmptyParameters = new ITokenParameter[0]; + private static readonly IList<ITokenParameter> EmptyParameters = + new ReadOnlyCollection<ITokenParameter>(new ITokenParameter[0]); public bool Equals(Token other) { @@ -37,7 +39,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation { return false; } - return (Parameters ?? EmptyParameters).SequenceEqual(other.Parameters ?? EmptyParameters); + return (Parameters).SequenceEqual(other.Parameters); } public override bool Equals(object obj) @@ -59,12 +61,15 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation public string Name { get; } - public ICollection<ITokenParameter> Parameters { get; } + /// <summary> + /// Returns the collection of parameters, can not be null. + /// </summary> + public IList<ITokenParameter> Parameters { get; } - public Token(string name, ICollection<ITokenParameter> parameters = null) + public Token(string name, IList<ITokenParameter> parameters = null) { Name = name; - Parameters = parameters; + Parameters = parameters ?? EmptyParameters; } public Token(string name, ITokenParameter parameter) http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/74a4f8af/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalEvaluationTests.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalEvaluationTests.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalEvaluationTests.cs index ea65cd2..0949ad5 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalEvaluationTests.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalEvaluationTests.cs @@ -22,12 +22,10 @@ #endregion using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Linq; +using Gremlin.Net.Structure; using Xunit; using Xunit.Abstractions; -using Xunit.Sdk; namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation { @@ -41,7 +39,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation } [Fact] - public void Traversal_Parser_Should_Parse_Parts() + public void Traversal_Parser_Should_Parse_Into_Tokens() { var items = new[] { @@ -64,7 +62,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation }; foreach (var item in items) { - var parts = TraversalParser.ParseParts(item.Item1); + var parts = TraversalParser.ParseTraversal(item.Item1); _output.WriteLine("Parsing " + item.Item1); if (parts[parts.Count-1].Parameters != null) { @@ -75,5 +73,20 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation Assert.Equal(new[] {new Token("g"), new Token("V")}.Concat(item.Item2), parts); } } + + [Fact] + public void GetTraversal_Should_Invoke_Traversal_Methods() + { + var traversalTexts = new string[] + { + "g.V().count()", + "g.V().constant(123)" + }; + var g = new Graph().Traversal(); + foreach (var text in traversalTexts) + { + TraversalParser.GetTraversal(text, g); + } + } } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/74a4f8af/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs index 0ac97ff..65ca103 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs @@ -56,49 +56,103 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation private static ITraversal BuildFromMethods(string traversalText, GraphTraversalSource g) { - var parts = traversalText.Split('.'); - if (parts[0] != "g") + var parts = ParseTraversal(traversalText); + if (parts[0].Name != "g") { throw BuildException(traversalText); } ITraversal traversal; - switch (parts[1]) + switch (parts[1].Name) { - case "V()": + case "V": + //TODO: support V() parameters traversal = g.V(); break; - case "E()": + case "E": traversal = g.E(); break; default: throw BuildException(traversalText); } - for (var i = 2; i < parts.Length; i++) + for (var i = 2; i < parts.Count; i++) { - var name = GetCsharpName(parts[i], traversalText); + var token = parts[i]; + var name = GetCsharpName(token.Name); var method = traversal.GetType().GetMethod(name); if (method == null) { throw new InvalidOperationException($"Traversal method '{parts[i]}' not found for testing"); } - if (method.IsGenericMethod) + var parameters = BuildParameters(method, token, out var genericParameters); + method = BuildGenericMethod(method, genericParameters); + traversal = (ITraversal) method.Invoke(traversal, parameters); + } + return traversal; + } + + private static MethodInfo BuildGenericMethod(MethodInfo method, IDictionary<string, Type> genericParameters) + { + if (!method.IsGenericMethod) + { + return method; + } + var genericArgs = method.GetGenericArguments(); + var types = new Type[genericArgs.Length]; + for (var i = 0; i < genericArgs.Length; i++) + { + var name = genericArgs[i].Name; + if (genericParameters.TryGetValue(name, out var type)) + { + types[i] = type; + } + //TODO: Try to infer it from the name based on modern schema + else { throw new InvalidOperationException( - $"Can not build traversal to test as '{name}()' method is generic"); + $"Can not build traversal to test as '{method.Name}()' method is generic and type '{name}'" + + $" can not be inferred"); } - traversal = (ITraversal) method.Invoke(traversal, new object[] { new object[0]}); } - return traversal; + return method.MakeGenericMethod(types); } - private static string GetCsharpName(string part, string traversalText) + private static object[] BuildParameters(MethodInfo method, Token token, + out IDictionary<string, Type> genericParameterTypes) { - if (!part.EndsWith("()")) + var paramsInfo = method.GetParameters(); + var parameters = new object[paramsInfo.Length]; + genericParameterTypes = new Dictionary<string, Type>(); + for (var i = 0; i < paramsInfo.Length; i++) { - throw BuildException(traversalText); + var info = paramsInfo[i]; + object value = null; + if (token.Parameters.Count > i) + { + var tokenParameter = token.Parameters[i]; + value = tokenParameter.GetValue(); + if (info.ParameterType.IsGenericParameter) + { + // We've provided a value for parameter of a generic type, we can infer the + // type of the generic argument based on the parameter. + // For example, in the case of `Constant<E2>(E2 value)` + // if we have the type of value we have the type of E2. + genericParameterTypes.Add(info.ParameterType.Name, tokenParameter.GetParameterType()); + } + } + parameters[i] = value ?? GetDefault(info.ParameterType); } + return parameters; + } + + public static object GetDefault(Type type) + { + return type.GetTypeInfo().IsValueType ? Activator.CreateInstance(type) : null; + } + + private static string GetCsharpName(string part) + { // Transform to PascalCasing and remove the parenthesis - return char.ToUpper(part[0]) + part.Substring(1, part.Length - 3); + return char.ToUpper(part[0]) + part.Substring(1); } private static Exception BuildException(string traversalText) @@ -106,7 +160,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation return new InvalidOperationException($"Can not build a traversal to test from '{traversalText}'"); } - internal static IList<Token> ParseParts(string traversalText) + internal static IList<Token> ParseTraversal(string traversalText) { var index = 0; return ParseTokens(traversalText, ref index); http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/74a4f8af/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalPredicateParameter.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalPredicateParameter.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalPredicateParameter.cs index d9bf061..8c040b9 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalPredicateParameter.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalPredicateParameter.cs @@ -24,6 +24,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Gremlin.Net.Process.Traversal; namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation { @@ -50,6 +51,16 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation return Parts != null ? Parts.GetHashCode() : 0; } + public object GetValue() + { + throw new NotImplementedException(); + } + + public Type GetParameterType() + { + return typeof(TraversalPredicate); + } + public IList<Token> Parts { get; } public TraversalPredicateParameter(IList<Token> parts) http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/74a4f8af/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalTokenParameter.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalTokenParameter.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalTokenParameter.cs index 5a5e8c1..9291cd6 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalTokenParameter.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalTokenParameter.cs @@ -24,6 +24,7 @@ using System; using System.Collections.Generic; using System.Linq; +using TEnum = Gremlin.Net.Process.Traversal.T; namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation { @@ -50,6 +51,16 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation return Parts != null ? Parts.GetHashCode() : 0; } + public object GetValue() + { + throw new NotImplementedException(); + } + + public Type GetParameterType() + { + return typeof(TEnum); + } + public IList<Token> Parts { get; } public TraversalTokenParameter(IList<Token> parts) http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/74a4f8af/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj index 40552ad..c929575 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj @@ -18,6 +18,7 @@ <ProjectReference Include="..\..\src\Gremlin.Net\Gremlin.Net.csproj" /> </ItemGroup> <ItemGroup> + <PackageReference Include="gherkin" Version="5.0.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" /> <PackageReference Include="xunit" Version="2.2.0" /> @@ -30,9 +31,4 @@ <ItemGroup> <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" /> </ItemGroup> - <ItemGroup> - <Reference Include="Gherkin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> - <HintPath>..\..\..\..\temp\Gherkin.dll</HintPath> - </Reference> - </ItemGroup> </Project> \ No newline at end of file