TINKERPOP-786 Added support for anonymous traversal generation
Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/e0505d12 Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/e0505d12 Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/e0505d12 Branch: refs/heads/tp32-glv Commit: e0505d1243f31789514ebc315ee19ee3568470a8 Parents: 5c7f0cb Author: Stephen Mallette <[email protected]> Authored: Tue May 9 16:43:00 2017 -0400 Committer: Stephen Mallette <[email protected]> Committed: Tue May 16 11:02:31 2017 -0400 ---------------------------------------------------------------------- docs/src/reference/the-traversal.asciidoc | 1 + .../src/main/java/SocialTraversalDsl.java | 10 ++ .../src/test/java/SocialDslTest.java | 6 + .../process/traversal/dsl/GremlinDsl.java | 1 + .../traversal/dsl/GremlinDslProcessor.java | 133 ++++++++++++++++++- 5 files changed, 147 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/e0505d12/docs/src/reference/the-traversal.asciidoc ---------------------------------------------------------------------- diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc index 1be365d..7da4015 100644 --- a/docs/src/reference/the-traversal.asciidoc +++ b/docs/src/reference/the-traversal.asciidoc @@ -3027,6 +3027,7 @@ The annotation processor will generate several classes for the DSL: interfaces (such as `GraphTraversal`) to instead return a `SocialTraversal` * `DefaultSocialTraversal` - A default implementation of `SocialTraversal` (typically not used directly by the user) * `SocialTraversalSource` - Spawns `DefaultSocialTraversal` instances. +* `__` - Spawns anonymous `DefaultSocialTraversal` instances. Using the DSL then just involves telling the `Graph` to use it: http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/e0505d12/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 af1f039..b7e4f07 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 @@ -22,6 +22,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.dsl.GremlinDsl; 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; +import org.apache.tinkerpop.gremlin.process.traversal.P; /** * This Social DSL is meant to be used with the TinkerPop "modern" toy graph. @@ -54,4 +55,13 @@ public interface SocialTraversalDsl<S, E> extends GraphTraversal.Admin<S, E> { public default <E2 extends Number> GraphTraversal<S, E2> youngestFriendsAge() { return out("knows").hasLabel("person").values("age").min(); } + + /** + * Designed to be used as a filter for "person" vertices based on the number of "created" edges encountered. + * + * @param number the minimum number of projects a person created + */ + public default GraphTraversal<S,Long> createdAtLeast(int number) { + return outE("created").count().is(P.gte(number)); + } } http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/e0505d12/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/src/test/java/SocialDslTest.java ---------------------------------------------------------------------- diff --git a/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/src/test/java/SocialDslTest.java b/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/src/test/java/SocialDslTest.java index 5967244..7f76558 100644 --- a/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/src/test/java/SocialDslTest.java +++ b/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/src/test/java/SocialDslTest.java @@ -51,4 +51,10 @@ public class SocialDslTest { SocialTraversalSource social = graph.traversal(SocialTraversalSource.class); assertEquals(4, social.persons().count().next().intValue()); } + + @Test + public void shouldFindAllPersonsWithTwoOrMoreProjects() { + SocialTraversalSource social = graph.traversal(SocialTraversalSource.class); + assertEquals(1, social.persons().filter(__.createdAtLeast(2)).count().next().intValue()); + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/e0505d12/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 df96007..d3a807f 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 @@ -38,6 +38,7 @@ import java.lang.annotation.Target; * <li>{@code SocialTraversal} - an interface that is an extension to {@code SocialTraversalDsl}</li> * <li>{@code DefaultSocialTraversal} - an implementation of the {@code SocialTraversal}</li> * <li>{@code SocialTraversalSource} - an extension of {@link GraphTraversalSource} which spawns {@code DefaultSocialTraversal} instances</li> + * <li>{@code __} - which spawns anonymous {@code DefaultSocialTraversal} instances</li> * </ul> * * Together these generated classes provide all the infrastructure required to properly Gremlin traversals enhanced http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/e0505d12/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 e246765..04e59b4 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 @@ -29,6 +29,7 @@ import com.squareup.javapoet.TypeVariableName; import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexStartStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep; import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversal; @@ -53,6 +54,7 @@ import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic; @@ -104,6 +106,9 @@ public class GremlinDslProcessor extends AbstractProcessor { // create the "TraversalSource" class which is used to spawn traversals from a Graph instance. It will // spawn instances of the "DefaultTraversal" generated above. generateTraversalSource(ctx); + + // create anonymous traversal for DSL + generateAnonymousTraversal(ctx); } } catch (Exception ex) { messager.printMessage(Diagnostic.Kind.ERROR, ex.getMessage()); @@ -112,6 +117,126 @@ public class GremlinDslProcessor extends AbstractProcessor { return true; } + private void generateAnonymousTraversal(final Context ctx) throws IOException { + final TypeSpec.Builder anonymousClass = TypeSpec.classBuilder("__") + .addModifiers(Modifier.PUBLIC, Modifier.FINAL); + + // this class is just static methods - it should not be instantiated + anonymousClass.addMethod(MethodSpec.constructorBuilder() + .addModifiers(Modifier.PRIVATE) + .build()); + + // add start() method + anonymousClass.addMethod(MethodSpec.methodBuilder("start") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addTypeVariable(TypeVariableName.get("A")) + .addStatement("return new $N<>()", ctx.defaultTraversalClazz) + .returns(ParameterizedTypeName.get(ctx.traversalClassName, TypeVariableName.get("A"), TypeVariableName.get("A"))) + .build()); + + // process the methods of the GremlinDsl annotated class + for (Element element : ctx.annotatedDslType.getEnclosedElements()) { + if (element.getKind() != ElementKind.METHOD) continue; + + final ExecutableElement templateMethod = (ExecutableElement) element; + final String methodName = templateMethod.getSimpleName().toString(); + + final TypeName returnType = 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 DeclaredType returnTypeMirror = (DeclaredType) templateMethod.getReturnType(); + final List<? extends TypeMirror> returnTypeArguments = returnTypeMirror.getTypeArguments(); + 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())); + }); + + boolean added = false; + final List<? extends VariableElement> parameters = templateMethod.getParameters(); + String body = "return __.<S>start().$L("; + for (VariableElement param : parameters) { + methodToAdd.addParameter(ParameterSpec.get(param)); + + body = body + param.getSimpleName() + ","; + added = true; + } + + // treat a final array as a varargs param + if (!parameters.isEmpty() && parameters.get(parameters.size() - 1).asType().getKind() == TypeKind.ARRAY) + methodToAdd.varargs(true); + + if (added) body = body.substring(0, body.length() - 1); + + body = body + ")"; + + methodToAdd.addStatement(body, methodName); + + anonymousClass.addMethod(methodToAdd.build()); + } + + // use methods from __ to template them into the DSL __ + final Element anonymousTraversal = elementUtils.getTypeElement(__.class.getCanonicalName()); + for (Element element : anonymousTraversal.getEnclosedElements()) { + if (element.getKind() != ElementKind.METHOD) continue; + + final ExecutableElement templateMethod = (ExecutableElement) element; + final String methodName = templateMethod.getSimpleName().toString(); + + // ignore start() from __ - that's not proxied + if (methodName.equals("start")) continue; + + final TypeName returnType = 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))); + + boolean added = false; + final List<? extends VariableElement> parameters = templateMethod.getParameters(); + String body; + if (methodName.equals("__")) { + for (VariableElement param : parameters) { + methodToAdd.addParameter(ParameterSpec.get(param)); + } + + methodToAdd.varargs(true); + + body = "return inject(starts)"; + } else { + body = "return __.<A>start().$L("; + for (VariableElement param : parameters) { + methodToAdd.addParameter(ParameterSpec.get(param)); + + body = body + param.getSimpleName() + ","; + added = true; + } + + // treat a final array as a varargs param + if (!parameters.isEmpty() && parameters.get(parameters.size() - 1).asType().getKind() == TypeKind.ARRAY) + methodToAdd.varargs(true); + + if (added) body = body.substring(0, body.length() - 1); + + body = body + ")"; + } + + methodToAdd.addStatement(body, methodName); + + anonymousClass.addMethod(methodToAdd.build()); + } + + final JavaFile traversalSourceJavaFile = JavaFile.builder(ctx.packageName, anonymousClass.build()).build(); + traversalSourceJavaFile.writeTo(filer); + } + private void generateTraversalSource(final Context ctx) throws IOException { final TypeElement graphTraversalSourceElement = ctx.traversalSourceDslType; final TypeSpec.Builder traversalSourceClass = TypeSpec.classBuilder(ctx.traversalSourceClazz) @@ -132,7 +257,7 @@ public class GremlinDslProcessor extends AbstractProcessor { .build()); // override methods to return a the DSL TraversalSource. find GraphTraversalSource class somewhere in the hierarchy - final Element tinkerPopsGraphTraversalSource = findTinkerPopsGraphTraversalSource(graphTraversalSourceElement); + final Element tinkerPopsGraphTraversalSource = findClassAsElement(graphTraversalSourceElement, GraphTraversalSource.class); for (Element elementOfGraphTraversalSource : tinkerPopsGraphTraversalSource.getEnclosedElements()) { // first copy/override methods that return a GraphTraversalSource so that we can instead return // the DSL TraversalSource class. @@ -226,13 +351,13 @@ public class GremlinDslProcessor extends AbstractProcessor { traversalSourceJavaFile.writeTo(filer); } - private Element findTinkerPopsGraphTraversalSource(final Element element) { - if (element.getSimpleName().contentEquals(GraphTraversalSource.class.getSimpleName())) { + private Element findClassAsElement(final Element element, final Class<?> clazz) { + if (element.getSimpleName().contentEquals(clazz.getSimpleName())) { return element; } final List<? extends TypeMirror> supertypes = typeUtils.directSupertypes(element.asType()); - return findTinkerPopsGraphTraversalSource(typeUtils.asElement(supertypes.get(0))); + return findClassAsElement(typeUtils.asElement(supertypes.get(0)), clazz); } private void generateDefaultTraversal(final Context ctx) throws IOException {
