TINKERPOP-768 Implemented basic DSL tooling WIP
Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/82c4255f Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/82c4255f Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/82c4255f Branch: refs/heads/TINKERPOP-786 Commit: 82c4255f16134964296dc43fccae790710dbff75 Parents: feffe79 Author: Stephen Mallette <sp...@genoprime.com> Authored: Wed Apr 26 07:01:53 2017 -0400 Committer: Stephen Mallette <sp...@genoprime.com> Committed: Wed May 3 09:10:39 2017 -0400 ---------------------------------------------------------------------- gremlin-archetype/gremlin-archetype-dsl/pom.xml | 44 +++ .../META-INF/maven/archetype-metadata.xml | 38 +++ .../archetype-resources/README.asciidoc | 33 ++ .../main/resources/archetype-resources/pom.xml | 63 ++++ .../src/main/java/SocialTraversalDsl.java | 41 +++ .../src/test/java/SocialDslTest.java | 46 +++ .../projects/standard/archetype.properties | 21 ++ .../test/resources/projects/standard/goal.txt | 1 + gremlin-archetype/pom.xml | 1 + gremlin-core/pom.xml | 36 +++ .../process/traversal/dsl/GremlinDsl.java | 36 +++ .../dsl/GremlinDslAnnotatedInterface.java | 32 ++ .../traversal/dsl/GremlinDslProcessor.java | 318 +++++++++++++++++++ .../traversal/dsl/ProcessorException.java | 37 +++ .../javax.annotation.processing.Processor | 1 + .../traversal/dsl/GremlinDslProcessorTest.java | 39 +++ .../traversal/dsl/SocialTraversalDsl.java | 43 +++ 17 files changed, 830 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/gremlin-archetype/gremlin-archetype-dsl/pom.xml ---------------------------------------------------------------------- diff --git a/gremlin-archetype/gremlin-archetype-dsl/pom.xml b/gremlin-archetype/gremlin-archetype-dsl/pom.xml new file mode 100644 index 0000000..d4922f5 --- /dev/null +++ b/gremlin-archetype/gremlin-archetype-dsl/pom.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>gremlin-archetype</artifactId> + <groupId>org.apache.tinkerpop</groupId> + <version>3.2.5-SNAPSHOT</version> + </parent> + + <artifactId>gremlin-archetype-dsl</artifactId> + <name>Apache TinkerPop :: Archetype - TinkerGraph</name> + <packaging>jar</packaging> + + <build> + <plugins> + <plugin> + <artifactId>maven-archetype-plugin</artifactId> + <version>2.4</version> + </plugin> + </plugins> + + <!-- apply variable substitution on the following files using variables from this pom --> + <resources> + <resource> + <directory>src/main/resources</directory> + <filtering>true</filtering> + <includes> + <include>archetype-resources/pom.xml</include> + <include>archetype-resources/README.asciidoc</include> + </includes> + </resource> + <resource> + <directory>src/main/resources</directory> + <filtering>false</filtering> + <excludes> + <exclude>archetype-resources/pom.xml</exclude> + <exclude>archetype-resources/README.asciidoc</exclude> + </excludes> + </resource> + </resources> + </build> +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/META-INF/maven/archetype-metadata.xml ---------------------------------------------------------------------- diff --git a/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/META-INF/maven/archetype-metadata.xml b/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/META-INF/maven/archetype-metadata.xml new file mode 100644 index 0000000..a159ee3 --- /dev/null +++ b/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/META-INF/maven/archetype-metadata.xml @@ -0,0 +1,38 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> +<archetype-descriptor xsi:schemaLocation="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.0.0 http://maven.apache.org/xsd/archetype-descriptor-1.0.0.xsd" name="gremlin-archetype-tinkergraph" + xmlns="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <fileSets> + <fileSet filtered="true"> + <directory></directory> + <includes> + <include>README.asciidoc</include> + </includes> + <excludes> + <exclude>**/*.xml</exclude> + </excludes> + </fileSet> + <fileSet filtered="true" packaged="true"> + <directory>src/main/java</directory> + </fileSet> + <fileSet filtered="true" packaged="true"> + <directory>src/test/java</directory> + </fileSet> + </fileSets> + +</archetype-descriptor> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/README.asciidoc ---------------------------------------------------------------------- diff --git a/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/README.asciidoc b/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/README.asciidoc new file mode 100644 index 0000000..1e131b1 --- /dev/null +++ b/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/README.asciidoc @@ -0,0 +1,33 @@ +//// +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +//// +Gremlin DSL +=========== + +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + +Prerequisites +------------- + +* Java 8 Update 40+ +* link:https://maven.apache.org/[Maven 3.x] + +Building and Running +-------------------- + +[source,text] +mvn clean package +mvn exec:java -Dexec.mainClass="${package}.App" \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/pom.xml ---------------------------------------------------------------------- diff --git a/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/pom.xml b/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/pom.xml new file mode 100644 index 0000000..d7bb31c --- /dev/null +++ b/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/pom.xml @@ -0,0 +1,63 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>${groupId}</groupId> + <artifactId>\${artifactId}</artifactId> + <version>${version}</version> + + <name>Social DSL: Modern Graph</name> + + <packaging>jar</packaging> + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + <dependencies> + <dependency> + <groupId>org.apache.tinkerpop</groupId> + <artifactId>tinkergraph-gremlin</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + <version>${slf4j.version}</version> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.12</version> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <!-- TinkerPop3 requires Java 8 --> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.3</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + </plugins> + </build> +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/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 new file mode 100644 index 0000000..546a328 --- /dev/null +++ b/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/src/main/java/SocialTraversalDsl.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package ${package}; + +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; + +@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); + } + + public default <E2 extends Number> GraphTraversal<S, E2> youngestFriendsAge() { + return out("knows").hasLabel("person").values("age").min(); + } + + @Override + public default GraphTraversal<S, E> iterate() { + GraphTraversal.Admin.super.iterate(); + return this; + } +} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/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 new file mode 100644 index 0000000..ddd584c --- /dev/null +++ b/gremlin-archetype/gremlin-archetype-dsl/src/main/resources/archetype-resources/src/test/java/SocialDslTest.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package ${package}; + +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class SocialDslTest { + + private Graph graph = TinkerFactory.createModern(); + + @Test + public void shouldValidateThatMarkoKnowsJosh() { + SocialTraversalSource social = graph.traversal(SocialTraversalSource.class); + assertTrue(social.V().has("name","marko").knows("josh").hasNext()); + } + + @Test + public void shouldGetAgeOfYoungestFriendOfMarko() { + SocialTraversalSource social = graph.traversal(SocialTraversalSource.class); + assertEquals(27, social.V().has("name","marko").youngestFriendsAge().next().intValue()); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/gremlin-archetype/gremlin-archetype-dsl/src/test/resources/projects/standard/archetype.properties ---------------------------------------------------------------------- diff --git a/gremlin-archetype/gremlin-archetype-dsl/src/test/resources/projects/standard/archetype.properties b/gremlin-archetype/gremlin-archetype-dsl/src/test/resources/projects/standard/archetype.properties new file mode 100644 index 0000000..cf98705 --- /dev/null +++ b/gremlin-archetype/gremlin-archetype-dsl/src/test/resources/projects/standard/archetype.properties @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +groupId=org.apache.tinkerpop +artifactId=gremlin-archetype-dsl +package=com.test.example +version=1.0.0-SNAPSHOT \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/gremlin-archetype/gremlin-archetype-dsl/src/test/resources/projects/standard/goal.txt ---------------------------------------------------------------------- diff --git a/gremlin-archetype/gremlin-archetype-dsl/src/test/resources/projects/standard/goal.txt b/gremlin-archetype/gremlin-archetype-dsl/src/test/resources/projects/standard/goal.txt new file mode 100644 index 0000000..4a1a71d --- /dev/null +++ b/gremlin-archetype/gremlin-archetype-dsl/src/test/resources/projects/standard/goal.txt @@ -0,0 +1 @@ +verify \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/gremlin-archetype/pom.xml ---------------------------------------------------------------------- diff --git a/gremlin-archetype/pom.xml b/gremlin-archetype/pom.xml index fbe808f..961fabc 100644 --- a/gremlin-archetype/pom.xml +++ b/gremlin-archetype/pom.xml @@ -30,6 +30,7 @@ limitations under the License. <modules> <module>gremlin-archetype-tinkergraph</module> <module>gremlin-archetype-server</module> + <module>gremlin-archetype-dsl</module> </modules> <build> http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/gremlin-core/pom.xml ---------------------------------------------------------------------- diff --git a/gremlin-core/pom.xml b/gremlin-core/pom.xml index 55047ab..989bf83 100644 --- a/gremlin-core/pom.xml +++ b/gremlin-core/pom.xml @@ -60,6 +60,11 @@ limitations under the License. </exclusion> </exclusions> </dependency> + <dependency> + <groupId>com.squareup</groupId> + <artifactId>javapoet</artifactId> + <version>1.8.0</version> + </dependency> <!-- LOGGING --> <dependency> <groupId>org.slf4j</groupId> @@ -96,6 +101,24 @@ limitations under the License. <artifactId>commons-collections</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>com.google.testing.compile</groupId> + <artifactId>compile-testing</artifactId> + <version>0.8</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.google.truth</groupId> + <artifactId>truth</artifactId> + <version>0.25</version> + <exclusions> + <!-- truth comes in through compile-testing and has a self-conflict - produces further conflict with hadoop-gremlin --> + <exclusion> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </exclusion> + </exclusions> + </dependency> </dependencies> <build> <directory>${basedir}/target</directory> @@ -115,6 +138,19 @@ limitations under the License. <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> </plugin> + <!-- disable annotation processing - testing of the GremlinDslProcessor is handled separately --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>1.8</source> + <target>1.8</target> + <compilerArgs> + <arg>-parameters</arg> + <arg>-proc:none</arg> + </compilerArgs> + </configuration> + </plugin> </plugins> </build> </project> http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/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 new file mode 100644 index 0000000..370ad9b --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDsl.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.process.traversal.dsl; + +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that specifies that an interface is meant to be used a DSL extension to a {@link GraphTraversal}. + * + * @author Stephen Mallette (http://stephen.genoprime.com) + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface GremlinDsl { +} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDslAnnotatedInterface.java ---------------------------------------------------------------------- diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDslAnnotatedInterface.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDslAnnotatedInterface.java new file mode 100644 index 0000000..2f6db74 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDslAnnotatedInterface.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.process.traversal.dsl; + +import javax.lang.model.element.TypeElement; + +/** + * @author Stephen Mallette (http://stephen.genoprime.com) + */ +class GremlinDslAnnotatedInterface { + private final TypeElement annotatedInterfaceElement; + + public GremlinDslAnnotatedInterface(final TypeElement interfaceElement) { + annotatedInterfaceElement = interfaceElement; + } +} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/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 new file mode 100644 index 0000000..d2bcc65 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDslProcessor.java @@ -0,0 +1,318 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.process.traversal.dsl; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeVariableName; +import jdk.nashorn.internal.codegen.types.Type; +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.step.map.AddVertexStartStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep; +import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversal; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Vertex; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Filer; +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +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.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; +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; + +/** + * @author Stephen Mallette (http://stephen.genoprime.com) + */ +@SupportedAnnotationTypes("org.apache.tinkerpop.gremlin.process.traversal.dsl.GremlinDsl") +@SupportedSourceVersion(SourceVersion.RELEASE_8) +public class GremlinDslProcessor extends AbstractProcessor { + private Messager messager; + private Elements elementUtils; + private Filer filer; + private Types typeUtils; + + @Override + public synchronized void init(final ProcessingEnvironment processingEnv) { + super.init(processingEnv); + messager = processingEnv.getMessager(); + elementUtils = processingEnv.getElementUtils(); + filer = processingEnv.getFiler(); + typeUtils = processingEnv.getTypeUtils(); + } + + @Override + public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) { + try { + for (Element dslElement : roundEnv.getElementsAnnotatedWith(GremlinDsl.class)) { + validateDSL(dslElement); + + final TypeElement annotatedDslType = (TypeElement) dslElement; + final String packageName = elementUtils.getPackageOf(dslElement).getQualifiedName().toString(); + + // create the Traversal implementation interface + final String dslName = dslElement.getSimpleName().toString(); + final String dslPrefix = dslName.substring(0, dslName.length() - "TraversalDSL".length()); // chop off "TraversalDSL" + final String traversalClazz = dslPrefix + "Traversal"; + final ClassName traversalClassName = ClassName.get(packageName, traversalClazz); + final String traversalSourceClazz = dslPrefix + "TraversalSource"; + final ClassName traversalSourceClassName = ClassName.get(packageName, traversalSourceClazz); + final String defaultTraversalClazz = "Default" + traversalClazz; + final ClassName defaultTraversalClassName = ClassName.get(packageName, defaultTraversalClazz); + final ClassName graphTraversalAdminClassName = ClassName.get(GraphTraversal.Admin.class); + + // START write "Traversal" class + final TypeSpec.Builder traversalInterface = TypeSpec.interfaceBuilder(traversalClazz) + .addModifiers(Modifier.PUBLIC) + .addTypeVariables(Arrays.asList(TypeVariableName.get("S"), TypeVariableName.get("E"))) + .addSuperinterface(TypeName.get(dslElement.asType())); + + // process the methods of the GremlinDsl annotated class + for (Element elementOfDsl : annotatedDslType.getEnclosedElements()) { + tryConstructMethod(elementOfDsl, traversalClassName, dslName, null, + Modifier.PUBLIC, Modifier.DEFAULT).ifPresent(traversalInterface::addMethod); + } + + // process the methods of GraphTraversal + final TypeElement graphTraversalElement = elementUtils.getTypeElement(GraphTraversal.class.getCanonicalName()); + for (Element elementOfGraphTraversal : graphTraversalElement.getEnclosedElements()) { + tryConstructMethod(elementOfGraphTraversal, traversalClassName, dslName, + e -> e.getSimpleName().contentEquals("asAdmin") || e.getSimpleName().contentEquals("iterate"), + Modifier.PUBLIC, Modifier.DEFAULT) + .ifPresent(traversalInterface::addMethod); + } + + final JavaFile traversalJavaFile = JavaFile.builder(packageName, traversalInterface.build()).build(); + traversalJavaFile.writeTo(filer); + // END write "Traversal" class + + // START write of the "DefaultTraversal" class + final TypeSpec.Builder defaultTraversalClass = TypeSpec.classBuilder(defaultTraversalClazz) + .addModifiers(Modifier.PUBLIC) + .addTypeVariables(Arrays.asList(TypeVariableName.get("S"), TypeVariableName.get("E"))) + .superclass(TypeName.get(elementUtils.getTypeElement(DefaultTraversal.class.getCanonicalName()).asType())) + .addSuperinterface(ParameterizedTypeName.get(traversalClassName, TypeVariableName.get("S"), TypeVariableName.get("E"))); + + // add the required constructors for instantiation + defaultTraversalClass.addMethod(MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addStatement("super()") + .build()); + defaultTraversalClass.addMethod(MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addParameter(Graph.class, "graph") + .addStatement("super($N)", "graph") + .build()); + defaultTraversalClass.addMethod(MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addParameter(traversalSourceClassName, "traversalSource") + .addStatement("super($N)", "traversalSource") + .build()); + + // add the override + defaultTraversalClass.addMethod(MethodSpec.methodBuilder("iterate") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addStatement("return ($T) super.iterate()", traversalClassName) + .returns(ParameterizedTypeName.get(traversalClassName, TypeVariableName.get("S"), TypeVariableName.get("E"))) + .build()); + defaultTraversalClass.addMethod(MethodSpec.methodBuilder("asAdmin") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addStatement("return ($T) super.asAdmin()", GraphTraversal.Admin.class) + .returns(ParameterizedTypeName.get(graphTraversalAdminClassName, TypeVariableName.get("S"), TypeVariableName.get("E"))) + .build()); + defaultTraversalClass.addMethod(MethodSpec.methodBuilder("clone") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addStatement("return ($T) super.clone()", defaultTraversalClassName) + .returns(ParameterizedTypeName.get(defaultTraversalClassName, TypeVariableName.get("S"), TypeVariableName.get("E"))) + .build()); + + final JavaFile defaultTraversalJavaFile = JavaFile.builder(packageName, defaultTraversalClass.build()).build(); + defaultTraversalJavaFile.writeTo(filer); + // END write of the "DefaultTraversal" class + + // START write "TraversalSource" class + final TypeElement graphTraversalSourceElement = elementUtils.getTypeElement(GraphTraversalSource.class.getCanonicalName()); + final TypeSpec.Builder traversalSourceClass = TypeSpec.classBuilder(traversalSourceClazz) + .addModifiers(Modifier.PUBLIC) + .superclass(TypeName.get(graphTraversalSourceElement.asType())); + + // add the required constructors for instantiation + traversalSourceClass.addMethod(MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addParameter(Graph.class, "graph") + .addStatement("super($N)", "graph") + .build()); + traversalSourceClass.addMethod(MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addParameter(Graph.class, "graph") + .addParameter(TraversalStrategies.class, "strategies") + .addStatement("super($N, $N)", "graph", "strategies") + .build()); + + // override methods to return a the DSL TraversalSource + for (Element elementOfGraphTraversal : graphTraversalSourceElement.getEnclosedElements()) { + // first copy/override methods that return a GraphTraversalSource so that we can instead return + // the DSL TraversalSource class. + tryConstructMethod(elementOfGraphTraversal, traversalSourceClassName, "", + e -> !(e.getReturnType().getKind() == TypeKind.DECLARED && ((DeclaredType) e.getReturnType()).asElement().getSimpleName().contentEquals(GraphTraversalSource.class.getSimpleName())), + Modifier.PUBLIC) + .ifPresent(traversalSourceClass::addMethod); + } + + // override methods that return GraphTraversal + traversalSourceClass.addMethod(MethodSpec.methodBuilder("addV") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addStatement("$N clone = this.clone()", traversalSourceClazz) + .addStatement("clone.bytecode.addStep($T.addV)", GraphTraversal.Symbols.class) + .addStatement("$N traversal = new $N(clone)", defaultTraversalClazz, defaultTraversalClazz) + .addStatement("return ($T) traversal.asAdmin().addStep(new $T(traversal, null))", traversalClassName, AddVertexStartStep.class) + .returns(ParameterizedTypeName.get(traversalClassName, ClassName.get(Vertex.class), ClassName.get(Vertex.class))) + .build()); + traversalSourceClass.addMethod(MethodSpec.methodBuilder("addV") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addParameter(String.class, "label") + .addStatement("$N clone = this.clone()", traversalSourceClazz) + .addStatement("clone.bytecode.addStep($T.addV, label)", GraphTraversal.Symbols.class) + .addStatement("$N traversal = new $N(clone)", defaultTraversalClazz, defaultTraversalClazz) + .addStatement("return ($T) traversal.asAdmin().addStep(new $T(traversal, label))", traversalClassName, AddVertexStartStep.class) + .returns(ParameterizedTypeName.get(traversalClassName, ClassName.get(Vertex.class), ClassName.get(Vertex.class))) + .build()); + traversalSourceClass.addMethod(MethodSpec.methodBuilder("V") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addParameter(Object[].class, "vertexIds") + .varargs(true) + .addStatement("$N clone = this.clone()", traversalSourceClazz) + .addStatement("clone.bytecode.addStep($T.V, vertexIds)", GraphTraversal.Symbols.class) + .addStatement("$N traversal = new $N(clone)", defaultTraversalClazz, defaultTraversalClazz) + .addStatement("return ($T) traversal.asAdmin().addStep(new $T(traversal, $T.class, true, vertexIds))", traversalClassName, GraphStep.class, Vertex.class) + .returns(ParameterizedTypeName.get(traversalClassName, ClassName.get(Vertex.class), ClassName.get(Vertex.class))) + .build()); + traversalSourceClass.addMethod(MethodSpec.methodBuilder("E") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addParameter(Object[].class, "edgeIds") + .varargs(true) + .addStatement("$N clone = this.clone()", traversalSourceClazz) + .addStatement("clone.bytecode.addStep($T.E, edgeIds)", GraphTraversal.Symbols.class) + .addStatement("$N traversal = new $N(clone)", defaultTraversalClazz, defaultTraversalClazz) + .addStatement("return ($T) traversal.asAdmin().addStep(new $T(traversal, $T.class, true, edgeIds))", traversalClassName, GraphStep.class, Edge.class) + .returns(ParameterizedTypeName.get(traversalClassName, ClassName.get(Edge.class), ClassName.get(Edge.class))) + .build()); + + final JavaFile traversalSourceJavaFile = JavaFile.builder(packageName, traversalSourceClass.build()).build(); + traversalSourceJavaFile.writeTo(filer); + // END write "TraversalSource" class + } + } catch (Exception ex) { + messager.printMessage(Diagnostic.Kind.ERROR, ex.getMessage()); + } + + return true; + } + + private Optional<MethodSpec> tryConstructMethod(final Element element, final ClassName returnClazz, final String parent, + final Predicate<ExecutableElement> ignore, final Modifier... modifiers) { + if (element.getKind() != ElementKind.METHOD) return Optional.empty(); + + final ExecutableElement templateMethod = (ExecutableElement) element; + final String methodName = templateMethod.getSimpleName().toString(); + + if (ignore != null && ignore.test(templateMethod)) return Optional.empty(); + + final DeclaredType returnTypeMirror = (DeclaredType) templateMethod.getReturnType(); + final List<? extends TypeMirror> returnTypeArguments = returnTypeMirror.getTypeArguments(); + + // build a return type with appropriate generic declarations (if such declarations are present) + final TypeName returnType = returnTypeArguments.isEmpty() ? + returnClazz : + ParameterizedTypeName.get(returnClazz, returnTypeArguments.stream().map(TypeName::get).collect(Collectors.toList()).toArray(new TypeName[returnTypeArguments.size()])); + final MethodSpec.Builder methodToAdd = MethodSpec.methodBuilder(methodName) + .addModifiers(modifiers) + .addAnnotation(Override.class) + .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(); + final String parentCall = parent.isEmpty() ? "" : parent + "."; + String body = "return (" + returnClazz.simpleName() + ") " + parentCall + "super." + methodName + "("; + 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); + + return Optional.of(methodToAdd.build()); + } + + private void validateDSL(final Element dslElement) throws ProcessorException { + if (dslElement.getKind() != ElementKind.INTERFACE) + throw new ProcessorException(dslElement, "Only interfaces can be annotated with @%s", GremlinDsl.class.getSimpleName()); + + final TypeElement typeElement = (TypeElement) dslElement; + if (!typeElement.getModifiers().contains(Modifier.PUBLIC)) + throw new ProcessorException(dslElement, "The interface %s is not public.", typeElement.getQualifiedName()); + } +} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/ProcessorException.java ---------------------------------------------------------------------- diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/ProcessorException.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/ProcessorException.java new file mode 100644 index 0000000..c84278e --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/ProcessorException.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.process.traversal.dsl; + +import javax.lang.model.element.Element; + +/** + * @author Stephen Mallette (http://stephen.genoprime.com) + */ +public class ProcessorException extends Exception { + private Element element; + + public ProcessorException(final Element element, final String msg, final Object... args) { + super(String.format(msg, args)); + this.element = element; + } + + public Element getElement() { + return element; + } +} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/gremlin-core/src/main/resources/META-INF/services/javax.annotation.processing.Processor ---------------------------------------------------------------------- diff --git a/gremlin-core/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/gremlin-core/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000..bd34c93 --- /dev/null +++ b/gremlin-core/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +org.apache.tinkerpop.gremlin.process.traversal.dsl.GremlinDslProcessor \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDslProcessorTest.java ---------------------------------------------------------------------- diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDslProcessorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDslProcessorTest.java new file mode 100644 index 0000000..c3c62cd --- /dev/null +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/GremlinDslProcessorTest.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.process.traversal.dsl; + +import com.google.testing.compile.JavaFileObjects; +import org.junit.Test; + +import static com.google.common.truth.Truth.ASSERT; +import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; + +/** + * @author Stephen Mallette (http://stephen.genoprime.com) + */ +public class GremlinDslProcessorTest { + + @Test + public void shouldCompile() { + ASSERT.about(javaSource()) + .that(JavaFileObjects.forResource(GremlinDsl.class.getResource("SocialTraversalDsl.java"))) + .processedWith(new GremlinDslProcessor()) + .compilesWithoutError(); + } +} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/82c4255f/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 new file mode 100644 index 0000000..af53dd2 --- /dev/null +++ b/gremlin-core/src/test/resources/org/apache/tinkerpop/gremlin/process/traversal/dsl/SocialTraversalDsl.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.util.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; + +/** + * @author Stephen Mallette (http://stephen.genoprime.com) + */ +@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); + } + + public default <E2 extends Number> GraphTraversal<S, E2> meanAgeOfFriends() { + return out("knows").hasLabel("person").properties("age").mean(); + } + + @Override + public default GraphTraversal<S, E> iterate() { + GraphTraversal.Admin.super.iterate(); + return this; + } +}