blob: 305717c9de25958ccb00cf602928f8b3de45f9c9 [file] [log] [blame]
/*
* 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.groovy.jsr223;
import org.apache.commons.configuration.MapConfiguration;
import org.apache.tinkerpop.gremlin.jsr223.TranslatorCustomizer;
import org.apache.tinkerpop.gremlin.process.traversal.Order;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
import org.apache.tinkerpop.gremlin.process.traversal.Translator;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
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.strategy.decoration.SubgraphStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.TranslationStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy;
import org.apache.tinkerpop.gremlin.structure.Column;
import org.apache.tinkerpop.gremlin.structure.Direction;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.structure.util.ElementHelper;
import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge;
import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex;
import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory;
import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
import org.apache.tinkerpop.gremlin.util.function.Lambda;
import org.junit.Test;
import javax.script.Bindings;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.UUID;
import java.util.function.Function;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
* @author Stephen Mallette (http://stephen.genoprime.com)
*/
public class GroovyTranslatorTest {
private Graph graph = TinkerGraph.open();
private GraphTraversalSource g = graph.traversal();
private static final GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine();
@Test
public void shouldHandleStrategies() throws Exception {
final TinkerGraph graph = TinkerFactory.createModern();
final GraphTraversalSource g = graph.traversal().withStrategies(SubgraphStrategy.create(new MapConfiguration(new HashMap<String, Object>() {{
put(SubgraphStrategy.VERTICES, __.has("name", "marko"));
}})));
final Bindings bindings = new SimpleBindings();
bindings.put("g", g);
Traversal.Admin<Vertex, Object> traversal = engine.eval(g.V().values("name").asAdmin().getBytecode(), bindings, "g");
assertEquals("marko", traversal.next());
assertFalse(traversal.hasNext());
//
traversal = engine.eval(g.withoutStrategies(SubgraphStrategy.class).V().count().asAdmin().getBytecode(), bindings, "g");
assertEquals(new Long(6), traversal.next());
assertFalse(traversal.hasNext());
//
traversal = engine.eval(g.withStrategies(SubgraphStrategy.create(new MapConfiguration(new HashMap<String, Object>() {{
put(SubgraphStrategy.VERTICES, __.has("name", "marko"));
}})), ReadOnlyStrategy.instance()).V().values("name").asAdmin().getBytecode(), bindings, "g");
assertEquals("marko", traversal.next());
assertFalse(traversal.hasNext());
}
@Test
public void shouldHandleConfusingSacks() {
final TinkerGraph graph = TinkerFactory.createModern();
final GraphTraversalSource g = graph.traversal();
final Traversal<Vertex,Double> tConstantUnary = g.withSack(1.0, Lambda.unaryOperator("it + 1")).V().sack();
final String scriptConstantUnary = GroovyTranslator.of("g").translate(tConstantUnary.asAdmin().getBytecode());
assertEquals("g.withSack(1.0d, (java.util.function.UnaryOperator) {it + 1}).V().sack()", scriptConstantUnary);
assertThatScriptOk(scriptConstantUnary, "g", g);
final Traversal<Vertex,Double> tSupplierUnary = g.withSack(Lambda.supplier("1.0d"), Lambda.<Double>unaryOperator("it + 1")).V().sack();
final String scriptSupplierUnary = GroovyTranslator.of("g").translate(tSupplierUnary.asAdmin().getBytecode());
assertEquals("g.withSack((java.util.function.Supplier) {1.0d}, (java.util.function.UnaryOperator) {it + 1}).V().sack()", scriptSupplierUnary);
assertThatScriptOk(scriptSupplierUnary, "g", g);
final Traversal<Vertex,Double> tConstantBinary = g.withSack(1.0, Lambda.binaryOperator("x,y -> x + y + 1")).V().sack();
final String scriptConstantBinary = GroovyTranslator.of("g").translate(tConstantBinary.asAdmin().getBytecode());
assertEquals("g.withSack(1.0d, (java.util.function.BinaryOperator) {x,y -> x + y + 1}).V().sack()", scriptConstantBinary);
assertThatScriptOk(scriptConstantBinary, "g", g);
final Traversal<Vertex,Double> tSupplierBinary = g.withSack(Lambda.supplier("1.0d"), Lambda.<Double>binaryOperator("x,y -> x + y + 1")).V().sack();
final String scriptSupplierBinary = GroovyTranslator.of("g").translate(tSupplierBinary.asAdmin().getBytecode());
assertEquals("g.withSack((java.util.function.Supplier) {1.0d}, (java.util.function.BinaryOperator) {x,y -> x + y + 1}).V().sack()", scriptSupplierBinary);
assertThatScriptOk(scriptSupplierBinary, "g", g);
}
@Test
public void shouldSupportStringSupplierLambdas() {
final TinkerGraph graph = TinkerFactory.createModern();
GraphTraversalSource g = graph.traversal();
g = g.withStrategies(new TranslationStrategy(g, GroovyTranslator.of("g"), false));
final GraphTraversal.Admin<Vertex, Integer> t = g.withSideEffect("lengthSum", 0).withSack(1)
.V()
.filter(Lambda.predicate("it.get().label().equals('person')"))
.flatMap(Lambda.function("it.get().vertices(Direction.OUT)"))
.map(Lambda.<Traverser<Object>, Integer>function("it.get().value('name').length()"))
.sideEffect(Lambda.consumer("{ x -> x.sideEffects(\"lengthSum\", x.<Integer>sideEffects('lengthSum') + x.get()) }"))
.order().by(Lambda.comparator("a,b -> a <=> b"))
.sack(Lambda.biFunction("{ a,b -> a + b }"))
.asAdmin();
final List<Integer> sacks = new ArrayList<>();
final List<Integer> lengths = new ArrayList<>();
while (t.hasNext()) {
final Traverser.Admin<Integer> traverser = t.nextTraverser();
sacks.add(traverser.sack());
lengths.add(traverser.get());
}
assertFalse(t.hasNext());
//
assertEquals(6, lengths.size());
assertEquals(3, lengths.get(0).intValue());
assertEquals(3, lengths.get(1).intValue());
assertEquals(3, lengths.get(2).intValue());
assertEquals(4, lengths.get(3).intValue());
assertEquals(5, lengths.get(4).intValue());
assertEquals(6, lengths.get(5).intValue());
///
assertEquals(6, sacks.size());
assertEquals(4, sacks.get(0).intValue());
assertEquals(4, sacks.get(1).intValue());
assertEquals(4, sacks.get(2).intValue());
assertEquals(5, sacks.get(3).intValue());
assertEquals(6, sacks.get(4).intValue());
assertEquals(7, sacks.get(5).intValue());
//
assertEquals(24, t.getSideEffects().<Number>get("lengthSum").intValue());
final String script = GroovyTranslator.of("g").translate(t.getBytecode());
assertEquals("g.withStrategies(org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.TranslationStrategy.instance())" +
".withSideEffect(\"lengthSum\",(int) 0).withSack((int) 1)" +
".V()" +
".filter({it.get().label().equals('person')})" +
".flatMap({it.get().vertices(Direction.OUT)})" +
".map({it.get().value('name').length()})" +
".sideEffect({ x -> x.sideEffects(\"lengthSum\", x.<Integer>sideEffects('lengthSum') + x.get()) })" +
".order().by({a,b -> a <=> b})" +
".sack({ a,b -> a + b })",
script);
}
@Test
public void shouldHandleMaps() {
final TinkerGraph graph = TinkerFactory.createModern();
final GraphTraversalSource g = graph.traversal();
final String script = GroovyTranslator.of("g").translate(g.V().id().is(new LinkedHashMap<Object,Object>() {{
put(3, "32");
put(Arrays.asList(1, 2, 3.1d), 4);
}}).asAdmin().getBytecode());
assertEquals("g.V().id().is([((int) 3):(\"32\"),([(int) 1, (int) 2, 3.1d]):((int) 4)])", script);
assertThatScriptOk(script, "g", g);
}
@Test
public void shouldHandleEmptyMaps() {
final TinkerGraph graph = TinkerFactory.createModern();
final GraphTraversalSource g = graph.traversal();
final Function identity = new Lambda.OneArgLambda("it.get()", "gremlin-groovy");
final String script = GroovyTranslator.of("g").translate(g.inject(Collections.emptyMap()).map(identity).asAdmin().getBytecode());
assertEquals("g.inject([]).map({it.get()})", script);
assertThatScriptOk(script, "g", g);
}
@Test
public void shouldHandleDate() {
final Calendar c = Calendar.getInstance();
c.set(1975, Calendar.SEPTEMBER, 7);
final Date d = c.getTime();
assertTranslation(String.format("new java.util.Date(%s)", d.getTime()), d);
}
@Test
public void shouldHandleTimestamp() {
final Calendar c = Calendar.getInstance();
c.set(1975, Calendar.SEPTEMBER, 7);
final Timestamp t = new Timestamp(c.getTime().getTime());
assertTranslation(String.format("new java.sql.Timestamp(%s)", t.getTime()), t);
}
@Test
public void shouldHandleUuid() {
final UUID uuid = UUID.fromString("ffffffff-fd49-1e4b-0000-00000d4b8a1d");
assertTranslation(String.format("java.util.UUID.fromString('%s')", uuid), uuid);
}
@Test
public void shouldHandleColumn() {
assertTranslation("Column.keys", Column.keys);
}
@Test
public void shouldHandleDirection() {
assertTranslation("Direction.BOTH", Direction.BOTH);
}
@Test
public void shouldHandleOrder() {
assertTranslation("Order.decr", Order.decr);
}
@Test
public void shouldHandlePop() {
assertTranslation("Pop.last", Pop.last);
}
@Test
public void shouldHandleScope() {
assertTranslation("Scope.local", Scope.local);
}
@Test
public void shouldOverrideDefaultTypeTranslationWithSomethingBonkers() {
final TinkerGraph graph = TinkerGraph.open();
final GraphTraversalSource g = graph.traversal();
final String thingToSuffixAllStringsWith = "-why-would-anyone-do-this";
final String script = GroovyTranslator.of("g", x -> x instanceof String ? x + thingToSuffixAllStringsWith : x).
translate(g.inject("yyy", "xxx").asAdmin().getBytecode());
assertEquals(String.format("g.inject(\"yyy%s\",\"xxx%s\")", thingToSuffixAllStringsWith, thingToSuffixAllStringsWith), script);
assertThatScriptOk(script, "g", g);
}
@Test
public void shouldIncludeCustomTypeTranslationForSomethingSilly() throws Exception {
final TinkerGraph graph = TinkerGraph.open();
final SillyClass notSillyEnough = SillyClass.from("not silly enough", 100);
final GraphTraversalSource g = graph.traversal();
// without type translation we get uglinesss
final String scriptBad = GroovyTranslator.of("g").
translate(g.inject(notSillyEnough).asAdmin().getBytecode());
assertEquals(String.format("g.inject(%s)", "not silly enough:100"), scriptBad);
// with type translation we get valid gremlin
final String scriptGood = GroovyTranslator.of("g",
x -> x instanceof SillyClass ?
new Translator.ScriptTranslator.Handled(String.format("org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyTranslatorTest.SillyClass.from('%s', (int) %s)",
((SillyClass) x).getX(), ((SillyClass) x).getY())) : x).
translate(g.inject(notSillyEnough).asAdmin().getBytecode());
assertEquals(String.format("g.inject(org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyTranslatorTest.SillyClass.from('%s', (int) %s))", notSillyEnough.getX(), notSillyEnough.getY()), scriptGood);
assertThatScriptOk(scriptGood, "g", g);
final GremlinGroovyScriptEngine customEngine = new GremlinGroovyScriptEngine(new SillyClassTranslatorCustomizer());
final Bindings b = new SimpleBindings();
b.put("g", g);
final Traversal t = customEngine.eval(g.inject(notSillyEnough).asAdmin().getBytecode(), b, "g");
final SillyClass sc = (SillyClass) t.next();
assertEquals(notSillyEnough.getX(), sc.getX());
assertEquals(notSillyEnough.getY(), sc.getY());
assertThat(t.hasNext(), is(false));
}
@Test
public void shouldHaveValidToString() {
assertEquals("translator[h:gremlin-groovy]", GroovyTranslator.of("h").toString());
}
@Test
public void shouldEscapeStrings() {
final TinkerGraph graph = TinkerFactory.createModern();
final GraphTraversalSource g = graph.traversal();
final String script = GroovyTranslator.of("g").translate(g.addV("customer")
.property("customer_id", 501L)
.property("name", "Foo\u0020Bar")
.property("age", 25)
.property("special", "`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?")
.asAdmin().getBytecode());
assertEquals("g.addV(\"customer\")" +
".property(\"customer_id\",501L)" +
".property(\"name\",\"Foo Bar\")" +
".property(\"age\",(int) 25)" +
".property(\"special\",\"\"\"`~!@#\\$%^&*()-_=+[{]}\\\\|;:'\\\",<.>/?\"\"\")",
script);
}
@Test
public void shouldHandleVertexAndEdge() {
final TinkerGraph graph = TinkerFactory.createModern();
final GraphTraversalSource g = graph.traversal();
final Object id1 = "customer:10:foo\u0020bar\u0020\u0024100#90"; // customer:10:foo bar $100#90
final Vertex vertex1 = DetachedVertex.build().setLabel("customer").setId(id1)
.create();
final String script1 = GroovyTranslator.of("g").translate(g.inject(vertex1).asAdmin().getBytecode());
assertEquals("g.inject(new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex(" +
"\"customer:10:foo bar \\$100#90\"," +
"\"customer\", Collections.emptyMap()))",
script1);
assertThatScriptOk(script1, "g", g);
final Object id2 = "user:20:foo\\u0020bar\\u005c\\u0022mr\\u005c\\u0022\\u00241000#50"; // user:20:foo\u0020bar\u005c\u0022mr\u005c\u0022\u00241000#50
final Vertex vertex2 = DetachedVertex.build().setLabel("user").setId(id2)
.create();
final String script2 = GroovyTranslator.of("g").translate(g.inject(vertex2).asAdmin().getBytecode());
assertEquals("g.inject(new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex(" +
"\"user:20:foo\\\\u0020bar\\\\u005c\\\\u0022mr\\\\u005c\\\\u0022\\\\u00241000#50\"," +
"\"user\", Collections.emptyMap()))",
script2);
assertThatScriptOk(script2, "g", g);
final Object id3 = "knows:30:foo\u0020bar\u0020\u0024100:\\u0020\\u0024500#70";
final Edge edge = DetachedEdge.build().setLabel("knows").setId(id3)
.setOutV((DetachedVertex) vertex1)
.setInV((DetachedVertex) vertex2)
.create();
final String script3 = GroovyTranslator.of("g").translate(g.inject(edge).asAdmin().getBytecode());
assertEquals("g.inject(new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge(" +
"\"knows:30:foo bar \\$100:\\\\u0020\\\\u0024500#70\"," +
"\"knows\",Collections.emptyMap()," +
"\"customer:10:foo bar \\$100#90\",\"customer\"," +
"\"user:20:foo\\\\u0020bar\\\\u005c\\\\u0022mr\\\\u005c\\\\u0022\\\\u00241000#50\",\"user\"))",
script3);
assertThatScriptOk(script3, "g", g);
final String script4 = GroovyTranslator.of("g").translate(
g.addE("knows").from(vertex1).to(vertex2).property("when", "2018/09/21")
.asAdmin().getBytecode());
assertEquals("g.addE(\"knows\")" +
".from(new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex(\"customer:10:foo bar \\$100#90\",\"customer\", Collections.emptyMap()))" +
".to(new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex(\"user:20:foo\\\\u0020bar\\\\u005c\\\\u0022mr\\\\u005c\\\\u0022\\\\u00241000#50\",\"user\", Collections.emptyMap()))" +
".property(\"when\",\"2018/09/21\")",
script4);
}
public static Object eval(final String s, final Object... args) throws ScriptException {
return engine.eval(s, new SimpleBindings(ElementHelper.asMap(args)));
}
public static Object eval(final String s, final Bindings b) throws ScriptException {
return engine.eval(s, b);
}
private void assertTranslation(final String expectedTranslation, final Object... objs) {
final String script = GroovyTranslator.of("g").translate(g.inject(objs).asAdmin().getBytecode());
assertEquals(String.format("g.inject(%s)", expectedTranslation), script);
assertThatScriptOk(script, "g", g);
}
private void assertThatScriptOk(final String s, final Object... args) {
try {
assertNotNull(eval(s, args));
} catch (ScriptException se) {
se.printStackTrace();
fail("Script should have eval'd");
}
}
public static class SillyClass {
private final String x;
private final int y;
private SillyClass(final String x, final int y) {
this.x = x;
this.y = y;
}
public static SillyClass from(final String x, final int y) {
return new SillyClass(x, y);
}
public String getX() {
return x;
}
public int getY() {
return y;
}
@Override
public String toString() {
return x + ":" + String.valueOf(y);
}
}
public static class SillyClassTranslatorCustomizer implements TranslatorCustomizer {
@Override
public Translator.ScriptTranslator.TypeTranslator createTypeTranslator() {
return x -> x instanceof SillyClass ?
new Translator.ScriptTranslator.Handled(String.format("org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyTranslatorTest.SillyClass.from('%s', (int) %s)",
((SillyClass) x).getX(), ((SillyClass) x).getY())) : x;
}
}
}