Repository: tinkerpop
Updated Branches:
  refs/heads/TINKERPOP-1791 [created] daa442cd7


TINKERPOP-1791 Added GremlinDsl.AnonymousMethod annotation

Allows the ability to bypass the type inference system and provides for direct 
specification of the type parameters when generating anonymous methods.


Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo
Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/daa442cd
Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/daa442cd
Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/daa442cd

Branch: refs/heads/TINKERPOP-1791
Commit: daa442cd7e100a7f48541cf17bc29b17c68cb5a5
Parents: 3648f80
Author: Stephen Mallette <sp...@genoprime.com>
Authored: Fri Oct 20 08:58:52 2017 -0400
Committer: Stephen Mallette <sp...@genoprime.com>
Committed: Fri Oct 20 08:58:52 2017 -0400

----------------------------------------------------------------------
 CHANGELOG.asciidoc                              |  1 +
 docs/src/reference/the-traversal.asciidoc       |  5 +-
 .../upgrade/release-3.2.x-incubating.asciidoc   | 56 ++++++++++++++++++-
 .../src/main/java/SocialTraversalDsl.java       | 13 ++++-
 .../process/traversal/dsl/GremlinDsl.java       | 20 +++++++
 .../traversal/dsl/GremlinDslProcessor.java      | 58 ++++++++++++++++----
 .../traversal/dsl/SocialTraversalDsl.java       | 19 ++++++-
 7 files changed, 156 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/daa442cd/CHANGELOG.asciidoc
----------------------------------------------------------------------
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 07f1d69..dd7624a 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -31,6 +31,7 @@ 
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 * Fixed a bug where bytecode containing lambdas would randomly select a 
traversal source from bindings.
 * Deprecated `GremlinScriptEngine.eval()` methods and replaced them with new 
overloads that include the specific `TraversalSource` to bind to.
 * Added `GraphHelper.cloneElements(Graph original, Graph clone)` to the 
`gremlin-test` module to quickly clone a graph.
+* Added `GremlinDsl.AnonymousMethod` annotation to help provide explicit types 
for anonymous methods when the types are not easily inferred.
 * Bump to GMavenPlus 1.6.
 * Added better error message for illegal use of `repeat()`-step.
 * Fixed a bug in `RangeByIsCountStrategy` that led to unexpected behaviors 
when predicates were used with floating point numbers.

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/daa442cd/docs/src/reference/the-traversal.asciidoc
----------------------------------------------------------------------
diff --git a/docs/src/reference/the-traversal.asciidoc 
b/docs/src/reference/the-traversal.asciidoc
index 167073a..632ed09 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -3034,7 +3034,10 @@ public interface SocialTraversalDsl<S, E> extends 
GraphTraversal.Admin<S, E> {
 ----
 
 IMPORTANT: Follow the TinkerPop convention of using `<S,E>` in naming generics 
as those conventions are taken into
-account when generating the anonymous traversal class.
+account when generating the anonymous traversal class. The processor attempts 
to infer the appropriate type parameters
+when generating the anonymous traversal class. If it cannot do it correctly, 
it is possible to avoid the inference by
+using the `GremlinDsl.AnonymousMethod` annotation on the DSL method. It allows 
explicit specification of the types to
+use.
 
 The `@GremlinDsl` annotation is used by the 
link:https://docs.oracle.com/javase/8/docs/api/index.html?javax/annotation/processing/Processor.html[Java
 Annotation Processor]
 to generate the boilerplate class structure required to properly use the DSL 
within the TinkerPop framework. These

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/daa442cd/docs/src/upgrade/release-3.2.x-incubating.asciidoc
----------------------------------------------------------------------
diff --git a/docs/src/upgrade/release-3.2.x-incubating.asciidoc 
b/docs/src/upgrade/release-3.2.x-incubating.asciidoc
index 732e29e..50226bb 100644
--- a/docs/src/upgrade/release-3.2.x-incubating.asciidoc
+++ b/docs/src/upgrade/release-3.2.x-incubating.asciidoc
@@ -29,7 +29,7 @@ 
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 
 Please see the 
link:https://github.com/apache/tinkerpop/blob/3.2.7/CHANGELOG.asciidoc#release-3-2-7[changelog]
 for a complete list of all the modifications that are part of this release.
 
-=== Embedded Remote Connection
+==== Embedded Remote Connection
 
 As Gremlin Language Variants (GLVs) expand their usage and use of 
`withRemote()` becomes more common, the need to mock
 the "remote" in unit tests increases. To simplify mocking in Java, the new 
`EmbeddedRemoteConnection` provides a
@@ -37,6 +37,60 @@ simple way to provide a "remote" that is actually local to 
the same JVM.
 
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-1756[TINKERPOP-1756]
 
+==== DSL Type Specification
+
+Prior to this version, the Java annotation processor for Gremlin DSLs has 
tried to infer the appropriate type
+specifications when generating anonymous methods. It largely performed this 
inference on simple conventions in the
+DSL method's template specification and there were times where it would fail. 
For example, a method like this:
+
+[source,java]
+----
+public default GraphTraversal<S, E> person() {
+    return hasLabel("person");
+}
+----
+
+would generate an anonymous method like:
+
+[source,java]
+----
+public static <S> SocialGraphTraversal<S, E> person() {
+    return hasLabel("person");
+}
+----
+
+and, of course, generate a compile error and `E` was not recognized as a 
symbol. The preferred generation would likely
+be:
+
+[source,java]
+----
+public static <S> SocialGraphTraversal<S, S> person() {
+    return hasLabel("person");
+}
+----
+
+To remedy this situation, a new annotation has been added which allows the 
user to control the type specifications
+more directly providing a way to avoid/override the inference system:
+
+[source,java]
+----
+@GremlinDsl.AnonymousMethod(returnTypeParameters = {"A", "A"}, 
methodTypeParameters = {"A"})
+public default GraphTraversal<S, E> person() {
+    return hasLabel("person");
+}
+----
+
+which will then generate:
+
+[source,java]
+----
+public static <A> SocialGraphTraversal<A, A> person() {
+    return hasLabel("person");
+}
+----
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-1791[TINKERPOP-1791]
+
 ==== Specify a Cluster Object
 
 The `:remote connect` command can now take a pre-defined `Cluster` object as 
its argument as opposed to a YAML

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/daa442cd/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/src/main/java/SocialTraversalDsl.java
----------------------------------------------------------------------
diff --git 
a/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/src/main/java/SocialTraversalDsl.java
 
b/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/src/main/java/SocialTraversalDsl.java
index 7f83152..e4d48bc 100644
--- 
a/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/src/main/java/SocialTraversalDsl.java
+++ 
b/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/src/main/java/SocialTraversalDsl.java
@@ -19,6 +19,7 @@
 package ${package};
 
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.GremlinDsl;
+import 
org.apache.tinkerpop.gremlin.process.traversal.dsl.GremlinDsl.AnonymousMethod;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
@@ -45,7 +46,7 @@ public interface SocialTraversalDsl<S, E> extends 
GraphTraversal.Admin<S, E> {
      * @param personName the name of the person to filter on
      */
     public default GraphTraversal<S, Vertex> knows(String personName) {
-        return out("knows").hasLabel("person").has("name", personName);
+        return ((SocialTraversalDsl) out("knows")).person().has("name", 
personName);
     }
 
     /**
@@ -53,7 +54,7 @@ public interface SocialTraversalDsl<S, E> extends 
GraphTraversal.Admin<S, E> {
      * those persons.
      */
     public default <E2 extends Number> GraphTraversal<S, E2> 
youngestFriendsAge() {
-        return out("knows").hasLabel("person").values("age").min();
+        return ((SocialTraversalDsl) 
out("knows")).person().values("age").min();
     }
 
     /**
@@ -64,4 +65,12 @@ public interface SocialTraversalDsl<S, E> extends 
GraphTraversal.Admin<S, E> {
     public default GraphTraversal<S, Long> createdAtLeast(int number) {
         return outE("created").count().is(P.gte(number));
     }
+
+    /**
+     * Filters objects by the "person" label. This step is designed to work 
with incoming vertices.
+     */
+    @GremlinDsl.AnonymousMethod(returnTypeParameters = {"A", "A"}, 
methodTypeParameters = {"A"})
+    public default GraphTraversal<S, E> person() {
+        return hasLabel("person");
+    }
 }

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/daa442cd/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDsl.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDsl.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDsl.java
index d3a807f..700c8ee 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDsl.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDsl.java
@@ -74,4 +74,24 @@ public @interface GremlinDsl {
      * </ul>
      */
     public boolean generateDefaultMethods() default true;
+
+    /**
+     * Annotation that allows the user to directly override the type 
parameters on generated anonymous methods. If this
+     * annotation is not specified then the processor will attempt to infer 
the correct type parameters to use when
+     * generating the anonymous method representations of the DSL methods.
+     */
+    @Target(ElementType.METHOD)
+    @Retention(RetentionPolicy.CLASS)
+    public @interface AnonymousMethod {
+
+        /**
+         * The type parameters to apply to the return type of the method 
applied in the order that they are specified.
+         */
+        public String[] returnTypeParameters() default {};
+
+        /**
+         * The type parameters to apply to the method in the order that they 
are specified.
+         */
+        public String[] methodTypeParameters() default {};
+    }
 }

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/daa442cd/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDslProcessor.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDslProcessor.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDslProcessor.java
index 470dd4b..cdfad6a 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDslProcessor.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDslProcessor.java
@@ -61,9 +61,11 @@ import javax.tools.Diagnostic;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * A custom Java annotation processor for the {@link GremlinDsl} annotation 
that helps to generate DSLs classes.
@@ -135,24 +137,39 @@ public class GremlinDslProcessor extends 
AbstractProcessor {
 
         // process the methods of the GremlinDsl annotated class
         for (ExecutableElement templateMethod : 
findMethodsOfElement(ctx.annotatedDslType, null)) {
+            final Optional<GremlinDsl.AnonymousMethod> methodAnnotation = 
Optional.ofNullable(templateMethod.getAnnotation(GremlinDsl.AnonymousMethod.class));
+
             final String methodName = 
templateMethod.getSimpleName().toString();
 
-            final TypeName returnType = 
getReturnTypeDefinition(ctx.traversalClassName, templateMethod);
+            // either use the direct return type of the DSL specification or 
override it with specification from
+            // GremlinDsl.AnonymousMethod
+            final TypeName returnType = methodAnnotation.isPresent() && 
methodAnnotation.get().returnTypeParameters().length > 0 ?
+                    getOverridenReturnTypeDefinition(ctx.traversalClassName, 
methodAnnotation.get().returnTypeParameters()) :
+                    getReturnTypeDefinition(ctx.traversalClassName, 
templateMethod);
+            
             final MethodSpec.Builder methodToAdd = 
MethodSpec.methodBuilder(methodName)
                     .addModifiers(Modifier.STATIC, Modifier.PUBLIC)
                     
.addExceptions(templateMethod.getThrownTypes().stream().map(TypeName::get).collect(Collectors.toList()))
                     .returns(returnType);
 
-            templateMethod.getTypeParameters().forEach(tp -> 
methodToAdd.addTypeVariable(TypeVariableName.get(tp)));
-
-            // might have to deal with an "S" (in __ it's usually an "A") - 
how to make this less bound to that convention?
-            final List<? extends TypeMirror> returnTypeArguments = 
getTypeArguments(templateMethod);
-            returnTypeArguments.stream().filter(rtm -> rtm instanceof 
TypeVariable).forEach(rtm -> {
-                if (((TypeVariable) 
rtm).asElement().getSimpleName().contentEquals("S"))
-                    
methodToAdd.addTypeVariable(TypeVariableName.get(((TypeVariable) 
rtm).asElement().getSimpleName().toString()));
-            });
+            // either use the method type parameter specified from the 
GremlinDsl.AnonymousMethod or just infer them
+            // from the DSL specification. "inferring" relies on convention 
and sometimes doesn't work for all cases.
+            final String startGeneric = methodAnnotation.isPresent() && 
methodAnnotation.get().methodTypeParameters().length > 0 ?
+                    methodAnnotation.get().methodTypeParameters()[0] : "S";
+            if (methodAnnotation.isPresent() && 
methodAnnotation.get().methodTypeParameters().length > 0)
+                
Stream.of(methodAnnotation.get().methodTypeParameters()).map(TypeVariableName::get).forEach(methodToAdd::addTypeVariable);
+            else {
+                templateMethod.getTypeParameters().forEach(tp -> 
methodToAdd.addTypeVariable(TypeVariableName.get(tp)));
+
+                // might have to deal with an "S" (in __ it's usually an "A") 
- how to make this less bound to that convention?
+                final List<? extends TypeMirror> returnTypeArguments = 
getTypeArguments(templateMethod);
+                returnTypeArguments.stream().filter(rtm -> rtm instanceof 
TypeVariable).forEach(rtm -> {
+                    if (((TypeVariable) 
rtm).asElement().getSimpleName().contentEquals("S"))
+                        
methodToAdd.addTypeVariable(TypeVariableName.get(((TypeVariable) 
rtm).asElement().getSimpleName().toString()));
+                });
+            }
 
-            addMethodBody(methodToAdd, templateMethod, "return 
__.<S>start().$L(", ")", methodName);
+            addMethodBody(methodToAdd, templateMethod, "return __.<" + 
startGeneric + ">start().$L(", ")", methodName);
             anonymousClass.addMethod(methodToAdd.build());
         }
 
@@ -425,6 +442,27 @@ public class GremlinDslProcessor extends AbstractProcessor 
{
         methodToAdd.addStatement(body, statementArgs);
     }
 
+    private TypeName getOverridenReturnTypeDefinition(final ClassName 
returnClazz, final String[] typeValues) {
+        return ParameterizedTypeName.get(returnClazz, 
Stream.of(typeValues).map(tv -> {
+            try {
+                return ClassName.get(Class.forName(tv));
+            } catch (ClassNotFoundException cnfe) {
+                if (tv.contains("extends")) {
+                    final String[] sides = tv.toString().split(" extends ");
+                    final TypeVariableName name = 
TypeVariableName.get(sides[0]);
+                    try {
+                        
name.withBounds(ClassName.get(Class.forName(sides[1])));
+                    } catch (Exception ex) {
+                        name.withBounds(TypeVariableName.get(sides[1]));
+                    }
+                    return name;
+                } else {
+                    return TypeVariableName.get(tv);
+                }
+            }
+        }).collect(Collectors.toList()).toArray(new 
TypeName[typeValues.length]));
+    }
+
     private TypeName getReturnTypeDefinition(final ClassName returnClazz, 
final ExecutableElement templateMethod) {
         final List<? extends TypeMirror> returnTypeArguments = 
getTypeArguments(templateMethod);
 

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/daa442cd/gremlin-core/src/test/resources/org/apache/tinkerpop/gremlin/process/traversal/dsl/SocialTraversalDsl.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/test/resources/org/apache/tinkerpop/gremlin/process/traversal/dsl/SocialTraversalDsl.java
 
b/gremlin-core/src/test/resources/org/apache/tinkerpop/gremlin/process/traversal/dsl/SocialTraversalDsl.java
index 4c31330..66db128 100644
--- 
a/gremlin-core/src/test/resources/org/apache/tinkerpop/gremlin/process/traversal/dsl/SocialTraversalDsl.java
+++ 
b/gremlin-core/src/test/resources/org/apache/tinkerpop/gremlin/process/traversal/dsl/SocialTraversalDsl.java
@@ -18,9 +18,9 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.dsl;
 
-import org.apache.tinkerpop.gremlin.process.traversal.dsl.GremlinDsl;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.process.traversal.P;
 
 /**
  * @author Stephen Mallette (http://stephen.genoprime.com)
@@ -28,10 +28,25 @@ import org.apache.tinkerpop.gremlin.structure.Vertex;
 @GremlinDsl
 public interface SocialTraversalDsl<S, E> extends GraphTraversal.Admin<S, E> {
     public default GraphTraversal<S, Vertex> knows(final String personName) {
-        return out("knows").hasLabel("person").has("name", personName);
+        return ((SocialTraversalDsl) out("knows")).person().has("name", 
personName);
     }
 
     public default <E2 extends Number> GraphTraversal<S, E2> 
meanAgeOfFriends() {
+        return ((SocialTraversalDsl) 
out("knows")).person().values("age").mean();
+    }
+
+    @GremlinDsl.AnonymousMethod(returnTypeParameters = {"A", "A"}, 
methodTypeParameters = {"A"})
+    public default GraphTraversal<S, E> person() {
+        return hasLabel("person");
+    }
+
+    @GremlinDsl.AnonymousMethod(returnTypeParameters = {"A", 
"org.apache.tinkerpop.gremlin.structure.Vertex"}, methodTypeParameters = {"A"})
+    public default GraphTraversal<S, Vertex> knowsOverride(final String 
personName) {
+        return out("knows").hasLabel("person").has("name", personName);
+    }
+
+    @GremlinDsl.AnonymousMethod(returnTypeParameters = {"A", "E2"}, 
methodTypeParameters = {"A", "E2 extends java.lang.Number"})
+    public default <E2 extends Number> GraphTraversal<S, E2> 
meanAgeOfFriendsOverride() {
         return out("knows").hasLabel("person").values("age").mean();
     }
 }

Reply via email to