You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2018/10/19 18:59:08 UTC

[tinkerpop] branch TINKERPOP-2072 created (now b1998da)

This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a change to branch TINKERPOP-2072
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git.


      at b1998da  TINKERPOP-2072 Refactored TypeTranslator for direction extension

This branch includes the following new commits:

     new 8a4c6ac  wip
     new b1998da  TINKERPOP-2072 Refactored TypeTranslator for direction extension

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[tinkerpop] 02/02: TINKERPOP-2072 Refactored TypeTranslator for direction extension

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2072
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit b1998da6e23271b404051cb334106c96e55f5f34
Author: Stephen Mallette <sp...@genoprime.com>
AuthorDate: Fri Oct 19 14:50:46 2018 -0400

    TINKERPOP-2072 Refactored TypeTranslator for direction extension
    
    This approach makes it much easier to extend TypeTranslators as the core functionality of the GroovyTranslator is itself a TypeTranslator. Simply extend that and go. Probably should have been this way from the beginning.
---
 CHANGELOG.asciidoc                                 |  1 +
 docs/src/upgrade/release-3.4.x.asciidoc            | 16 ++++++++
 .../gremlin/process/traversal/Translator.java      | 29 ++------------
 .../groovy/jsr223/GremlinGroovyScriptEngine.java   |  2 +-
 .../gremlin/groovy/jsr223/GroovyTranslator.java    | 45 ++++++----------------
 .../groovy/jsr223/GroovyTranslatorTest.java        | 33 +++++++---------
 6 files changed, 47 insertions(+), 79 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index ec6cae7..d6a8dfb 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -40,6 +40,7 @@ This release also includes changes from <<release-3-3-3, 3.3.3>>.
 * Deprecated `Graph.io()` and related infrastructure.
 * `GraphMLReader` better handles edge and vertex properties with the same name.
 * Maintained order of annotations in metrics returned from `profile()`-step.
+* Refactored `TypeTranslator` to be directly extensible for `ScriptTranslator` functions.
 * Bumped to Netty 4.1.25.
 * Bumped to Spark 2.3.1.
 * Bumped to Groovy 2.5.2.
diff --git a/docs/src/upgrade/release-3.4.x.asciidoc b/docs/src/upgrade/release-3.4.x.asciidoc
index cafcc46..2023bfd 100644
--- a/docs/src/upgrade/release-3.4.x.asciidoc
+++ b/docs/src/upgrade/release-3.4.x.asciidoc
@@ -600,3 +600,19 @@ The extent to which TinkerPop tests "upsert" is fairly narrow. Graph providers t
 should consider their own test suites carefully to ensure appropriate coverage.
 
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-1685[TINKERPOP-1685]
+
+==== TypeTranslator Changes
+
+The `TypeTranslator` experienced a change in its API and `GroovyTranslator` a change in expectations.
+
+`TypeTranslator` now implements `BiFunction` and takes the graph traversal source name as an argument along with the
+object to translate. This interface is implemented by default for Groovy with `GroovyTranslator.DefaultTypeTranslator`
+which encapsulates all the functionality of what `GroovyTranslator` formerly did by default. To provide customize
+translation, simply extend the `DefaultTypeTranslator` and override the methods.
+
+`GroovyTranslator` now expects that the `TypeTranslator` provider to it as part of its `of()` static method overload
+is "complete" - i.e. that it provides all the functionality to translate the types passed to it. Thus, a "complete"
+`TypeTranslator` is one that does everything that `DefaultTypeTranslator` does as a minimum requirement. Therefore,
+the extension model described above is the easiest way to get going with a custom `TypeTranslator` approach.
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2072[TINKERPOP-2072]
\ No newline at end of file
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Translator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Translator.java
index 0346092..8cdd84d 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Translator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Translator.java
@@ -19,7 +19,7 @@
 
 package org.apache.tinkerpop.gremlin.process.traversal;
 
-import java.util.function.UnaryOperator;
+import java.util.function.BiFunction;
 
 /**
  * A Translator will translate {@link Bytecode} into another representation. That representation may be a
@@ -63,31 +63,10 @@ public interface Translator<S, T> {
     public interface ScriptTranslator extends Translator<String, String> {
 
         /**
-         * Provides a way to customize and override the standard translation process. A {@link ScriptTranslator}
-         * implementation can choose to expose a way to accept a {@code TypeTranslator} which will convert an incoming
-         * object to a different form which will then be normally processed or can return a {@link Handled} object
-         * with the already translated script.
+         * Provides a way for the {@link ScriptTranslator} to convert various data types to their string
+         * representations in their target language.
          */
-        public interface TypeTranslator extends UnaryOperator<Object> {
-            public static TypeTranslator identity() {
-                return t -> t;
-            }
-        }
-
-        /**
-         * Contains a completed type translation from the {@link TypeTranslator}.
-         */
-        public class Handled {
-            private final String translation;
-
-            public Handled(final String translation) {
-                this.translation = translation;
-            }
-
-            public String getTranslation() {
-                return translation;
-            }
-        }
+        public interface TypeTranslator extends BiFunction<String, Object, Object> { }
     }
 
     /**
diff --git a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngine.java b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngine.java
index dae427c..0cd55be 100644
--- a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngine.java
+++ b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngine.java
@@ -278,7 +278,7 @@ public class GremlinGroovyScriptEngine extends GroovyScriptEngineImpl implements
                 filter(p -> p instanceof TranslatorCustomizer).
                 map(p -> (TranslatorCustomizer) p).findFirst();
         typeTranslator = translatorCustomizer.isPresent() ? translatorCustomizer.get().createTypeTranslator() :
-                Translator.ScriptTranslator.TypeTranslator.identity();
+                new GroovyTranslator.DefaultTypeTranslator();
 
         createClassLoader();
     }
diff --git a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslator.java b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslator.java
index 64dac56..b961821 100644
--- a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslator.java
+++ b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslator.java
@@ -62,22 +62,23 @@ public final class GroovyTranslator implements Translator.ScriptTranslator {
 
     private GroovyTranslator(final String traversalSource, final TypeTranslator typeTranslator) {
         this.traversalSource = traversalSource;
-        this.typeTranslator = new DefaultTypeTranslator(traversalSource, typeTranslator);
+        this.typeTranslator = typeTranslator;
     }
 
     public static final GroovyTranslator of(final String traversalSource) {
-        return of(traversalSource, TypeTranslator.identity());
+        return of(traversalSource, null);
     }
 
     public static final GroovyTranslator of(final String traversalSource, final TypeTranslator typeTranslator) {
-        return new GroovyTranslator(traversalSource, Optional.ofNullable(typeTranslator).orElse(TypeTranslator.identity()));
+        return new GroovyTranslator(traversalSource,
+                Optional.ofNullable(typeTranslator).orElseGet(DefaultTypeTranslator::new));
     }
 
     ///////
 
     @Override
     public String translate(final Bytecode bytecode) {
-        return typeTranslator.apply(bytecode).toString();
+        return typeTranslator.apply(traversalSource, bytecode).toString();
     }
 
     @Override
@@ -100,40 +101,16 @@ public final class GroovyTranslator implements Translator.ScriptTranslator {
      */
     public static class DefaultTypeTranslator implements TypeTranslator {
 
-        private final String traversalSource;
-        private TypeTranslator customTypeTranslator;
-
-        public DefaultTypeTranslator() {
-            this("__", TypeTranslator.identity());
-        }
-
-        public DefaultTypeTranslator(final String traversalSource) {
-            this(traversalSource, TypeTranslator.identity());
-        }
-
-        public DefaultTypeTranslator(final String traversalSource, final TypeTranslator customTypeTranslator) {
-            this.traversalSource = traversalSource;
-            this.customTypeTranslator = customTypeTranslator;
-        }
-
         @Override
-        public Object apply(final Object o) {
+        public Object apply(final String traversalSource, final Object o) {
             if (o instanceof Bytecode)
                 return internalTranslate(traversalSource, (Bytecode) o);
             else
                 return convertToString(o);
         }
 
-        protected String convertToString(final Object o) {
-
-            final Object object = customTypeTranslator.apply(o);
-
-            // an object that is Handled means that the "custom" TypeTranslator figured out how to convert the
-            // object to a string and it should be used as-is, otherwise it gets passed down the line through the normal
-            // process
-            if (object instanceof Handled)
-                return ((Handled) object).getTranslation();
-            else if (object instanceof Bytecode.Binding)
+        protected String convertToString(final Object object) {
+            if (object instanceof Bytecode.Binding)
                 return ((Bytecode.Binding) object).variable();
             else if (object instanceof Bytecode)
                 return internalTranslate("__", (Bytecode) object);
@@ -235,7 +212,7 @@ public final class GroovyTranslator implements Translator.ScriptTranslator {
                 return null == object ? "null" : object.toString();
         }
 
-        private String internalTranslate(final String start, final Bytecode bytecode) {
+        protected String internalTranslate(final String start, final Bytecode bytecode) {
             final StringBuilder traversalScript = new StringBuilder(start);
             for (final Bytecode.Instruction instruction : bytecode.getInstructions()) {
                 final String methodName = instruction.getOperator();
@@ -253,7 +230,7 @@ public final class GroovyTranslator implements Translator.ScriptTranslator {
             return traversalScript.toString();
         }
 
-        private StringBuilder convertPToString(final P p, final StringBuilder current) {
+        protected StringBuilder convertPToString(final P p, final StringBuilder current) {
             if (p instanceof TextP) return convertTextPToString((TextP) p, current);
             if (p instanceof ConnectiveP) {
                 final List<P<?>> list = ((ConnectiveP) p).getPredicates();
@@ -268,7 +245,7 @@ public final class GroovyTranslator implements Translator.ScriptTranslator {
             return current;
         }
 
-        private StringBuilder convertTextPToString(final TextP p, final StringBuilder current) {
+        protected StringBuilder convertTextPToString(final TextP p, final StringBuilder current) {
             current.append("TextP.").append(p.getBiPredicate().toString()).append("(").append(convertToString(p.getValue())).append(")");
             return current;
         }
diff --git a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslatorTest.java b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslatorTest.java
index 2cbc962..e56b105 100644
--- a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslatorTest.java
+++ b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslatorTest.java
@@ -20,7 +20,6 @@
 package org.apache.tinkerpop.gremlin.groovy.jsr223;
 
 import org.apache.commons.configuration.MapConfiguration;
-import org.apache.tinkerpop.gremlin.AbstractGremlinTest;
 import org.apache.tinkerpop.gremlin.jsr223.TranslatorCustomizer;
 import org.apache.tinkerpop.gremlin.process.traversal.Order;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
@@ -225,17 +224,6 @@ public class GroovyTranslatorTest {
     }
 
     @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);
@@ -247,10 +235,7 @@ public class GroovyTranslatorTest {
         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).
+        final String scriptGood = GroovyTranslator.of("g", new SillyClassTranslator()).
                 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);
@@ -389,13 +374,23 @@ public class GroovyTranslatorTest {
         }
     }
 
+    public static class SillyClassTranslator extends GroovyTranslator.DefaultTypeTranslator {
+
+        @Override
+        protected String convertToString(final Object object) {
+            if (object instanceof SillyClass)
+                return String.format("org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyTranslatorTest.SillyClass.from('%s', (int) %s)",
+                        ((SillyClass) object).getX(), ((SillyClass) object).getY());
+            else
+                return super.convertToString(object);
+        }
+    }
+
     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;
+            return new SillyClassTranslator();
         }
     }
 }


[tinkerpop] 01/02: wip

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2072
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 8a4c6acb58e268e4138102899f52277053d6c5d6
Author: Stephen Mallette <sp...@genoprime.com>
AuthorDate: Fri Oct 19 11:42:58 2018 -0400

    wip
---
 .../gremlin/groovy/jsr223/GroovyTranslator.java    | 313 +++++++++++----------
 1 file changed, 171 insertions(+), 142 deletions(-)

diff --git a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslator.java b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslator.java
index ae892f3..64dac56 100644
--- a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslator.java
+++ b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslator.java
@@ -62,7 +62,7 @@ public final class GroovyTranslator implements Translator.ScriptTranslator {
 
     private GroovyTranslator(final String traversalSource, final TypeTranslator typeTranslator) {
         this.traversalSource = traversalSource;
-        this.typeTranslator = typeTranslator;
+        this.typeTranslator = new DefaultTypeTranslator(traversalSource, typeTranslator);
     }
 
     public static final GroovyTranslator of(final String traversalSource) {
@@ -77,7 +77,7 @@ public final class GroovyTranslator implements Translator.ScriptTranslator {
 
     @Override
     public String translate(final Bytecode bytecode) {
-        return this.internalTranslate(this.traversalSource, bytecode);
+        return typeTranslator.apply(bytecode).toString();
     }
 
     @Override
@@ -95,153 +95,182 @@ public final class GroovyTranslator implements Translator.ScriptTranslator {
         return this.traversalSource;
     }
 
-    ///////
+    /**
+     * Performs standard type translation for the TinkerPop types to Groovy.
+     */
+    public static class DefaultTypeTranslator implements TypeTranslator {
 
-    private String internalTranslate(final String start, final Bytecode bytecode) {
-        final StringBuilder traversalScript = new StringBuilder(start);
-        for (final Bytecode.Instruction instruction : bytecode.getInstructions()) {
-            final String methodName = instruction.getOperator();
-            if (0 == instruction.getArguments().length)
-                traversalScript.append(".").append(methodName).append("()");
-            else {
-                traversalScript.append(".");
-                String temp = methodName + "(";
-                for (final Object object : instruction.getArguments()) {
-                    temp = temp + convertToString(object) + ",";
-                }
-                traversalScript.append(temp.substring(0, temp.length() - 1)).append(")");
-            }
+        private final String traversalSource;
+        private TypeTranslator customTypeTranslator;
+
+        public DefaultTypeTranslator() {
+            this("__", TypeTranslator.identity());
         }
-        return traversalScript.toString();
-    }
 
-    private String convertToString(final Object o) {
-        // a TypeTranslator that returns Handled means that the typetranslator figure out how to convert the
-        // object to a string and it should be used as-is, otherwise it gets passed down the line through the normal
-        // process
-        final Object object = typeTranslator.apply(o);
-
-        if (object instanceof Handled)
-            return ((Handled) object).getTranslation();
-        else if (object instanceof Bytecode.Binding)
-            return ((Bytecode.Binding) object).variable();
-        else if (object instanceof Bytecode)
-            return this.internalTranslate("__", (Bytecode) object);
-        else if (object instanceof Traversal)
-            return convertToString(((Traversal) object).asAdmin().getBytecode());
-        else if (object instanceof String) {
-            return (((String) object).contains("\"") ? "\"\"\"" + StringEscapeUtils.escapeJava((String) object) + "\"\"\"" : "\"" + StringEscapeUtils.escapeJava((String) object) + "\"")
-                    .replace("$", "\\$");
-        } else if (object instanceof Set) {
-            final Set<String> set = new HashSet<>(((Set) object).size());
-            for (final Object item : (Set) object) {
-                set.add(convertToString(item));
-            }
-            return set.toString() + " as Set";
-        } else if (object instanceof List) {
-            final List<String> list = new ArrayList<>(((List) object).size());
-            for (final Object item : (List) object) {
-                list.add(convertToString(item));
-            }
-            return list.toString();
-        } else if (object instanceof Map) {
-            final StringBuilder map = new StringBuilder("[");
-            for (final Map.Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) {
-                map.append("(").
-                        append(convertToString(entry.getKey())).
-                        append("):(").
-                        append(convertToString(entry.getValue())).
-                        append("),");
-            }
+        public DefaultTypeTranslator(final String traversalSource) {
+            this(traversalSource, TypeTranslator.identity());
+        }
 
-            // only need to remove this last bit if entries were added
-            if (!((Map<?, ?>) object).isEmpty())
-                map.deleteCharAt(map.length() - 1);
-
-            return map.append("]").toString();
-        } else if (object instanceof Long)
-            return object + "L";
-        else if (object instanceof Double)
-            return object + "d";
-        else if (object instanceof Float)
-            return object + "f";
-        else if (object instanceof Integer)
-            return "(int) " + object;
-        else if (object instanceof Class)
-            return ((Class) object).getCanonicalName();
-        else if (object instanceof Timestamp)
-            return "new java.sql.Timestamp(" + ((Timestamp) object).getTime() + ")";
-        else if (object instanceof Date)
-            return "new java.util.Date(" + ((Date) object).getTime() + ")";
-        else if (object instanceof UUID)
-            return "java.util.UUID.fromString('" + object.toString() + "')";
-        else if (object instanceof P)
-            return convertPToString((P) object, new StringBuilder()).toString();
-        else if (object instanceof SackFunctions.Barrier)
-            return "SackFunctions.Barrier." + object.toString();
-        else if (object instanceof VertexProperty.Cardinality)
-            return "VertexProperty.Cardinality." + object.toString();
-        else if (object instanceof TraversalOptionParent.Pick)
-            return "TraversalOptionParent.Pick." + object.toString();
-        else if (object instanceof Enum)
-            return ((Enum) object).getDeclaringClass().getSimpleName() + "." + object.toString();
-        else if (object instanceof Element) {
-            if (object instanceof Vertex) {
-                final Vertex vertex = (Vertex) object;
-                return "new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex(" +
-                        convertToString(vertex.id()) + "," +
-                        convertToString(vertex.label()) + ", Collections.emptyMap())";
-            } else if (object instanceof Edge) {
-                final Edge edge = (Edge) object;
-                return "new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge(" +
-                        convertToString(edge.id()) + "," +
-                        convertToString(edge.label()) + "," +
-                        "Collections.emptyMap()," +
-                        convertToString(edge.outVertex().id()) + "," +
-                        convertToString(edge.outVertex().label()) + "," +
-                        convertToString(edge.inVertex().id()) + "," +
-                        convertToString(edge.inVertex().label()) + ")";
-            } else {// VertexProperty
-                final VertexProperty vertexProperty = (VertexProperty) object;
-                return "new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertexProperty(" +
-                        convertToString(vertexProperty.id()) + "," +
-                        convertToString(vertexProperty.label()) + "," +
-                        convertToString(vertexProperty.value()) + "," +
-                        "Collections.emptyMap()," +
-                        convertToString(vertexProperty.element()) + ")";
-            }
-        } else if (object instanceof Lambda) {
-            final String lambdaString = ((Lambda) object).getLambdaScript().trim();
-            return lambdaString.startsWith("{") ? lambdaString : "{" + lambdaString + "}";
-        } else if (object instanceof TraversalStrategyProxy) {
-            final TraversalStrategyProxy proxy = (TraversalStrategyProxy) object;
-            if (proxy.getConfiguration().isEmpty())
-                return proxy.getStrategyClass().getCanonicalName() + ".instance()";
+        public DefaultTypeTranslator(final String traversalSource, final TypeTranslator customTypeTranslator) {
+            this.traversalSource = traversalSource;
+            this.customTypeTranslator = customTypeTranslator;
+        }
+
+        @Override
+        public Object apply(final Object o) {
+            if (o instanceof Bytecode)
+                return internalTranslate(traversalSource, (Bytecode) o);
             else
-                return proxy.getStrategyClass().getCanonicalName() + ".create(new org.apache.commons.configuration.MapConfiguration(" + convertToString(ConfigurationConverter.getMap(proxy.getConfiguration())) + "))";
-        } else if (object instanceof TraversalStrategy) {
-            return convertToString(new TraversalStrategyProxy(((TraversalStrategy) object)));
-        } else
-            return null == object ? "null" : object.toString();
-    }
+                return convertToString(o);
+        }
+
+        protected String convertToString(final Object o) {
+
+            final Object object = customTypeTranslator.apply(o);
 
-    private StringBuilder convertPToString(final P p, final StringBuilder current) {
-        if (p instanceof TextP) return convertTextPToString((TextP) p, current);
-        if (p instanceof ConnectiveP) {
-            final List<P<?>> list = ((ConnectiveP) p).getPredicates();
-            for (int i = 0; i < list.size(); i++) {
-                convertPToString(list.get(i), current);
-                if (i < list.size() - 1)
-                    current.append(p instanceof OrP ? ".or(" : ".and(");
+            // an object that is Handled means that the "custom" TypeTranslator figured out how to convert the
+            // object to a string and it should be used as-is, otherwise it gets passed down the line through the normal
+            // process
+            if (object instanceof Handled)
+                return ((Handled) object).getTranslation();
+            else if (object instanceof Bytecode.Binding)
+                return ((Bytecode.Binding) object).variable();
+            else if (object instanceof Bytecode)
+                return internalTranslate("__", (Bytecode) object);
+            else if (object instanceof Traversal)
+                return convertToString(((Traversal) object).asAdmin().getBytecode());
+            else if (object instanceof String) {
+                return (((String) object).contains("\"") ? "\"\"\"" + StringEscapeUtils.escapeJava((String) object) + "\"\"\"" : "\"" + StringEscapeUtils.escapeJava((String) object) + "\"")
+                        .replace("$", "\\$");
+            } else if (object instanceof Set) {
+                final Set<String> set = new HashSet<>(((Set) object).size());
+                for (final Object item : (Set) object) {
+                    set.add(convertToString(item));
+                }
+                return set.toString() + " as Set";
+            } else if (object instanceof List) {
+                final List<String> list = new ArrayList<>(((List) object).size());
+                for (final Object item : (List) object) {
+                    list.add(convertToString(item));
+                }
+                return list.toString();
+            } else if (object instanceof Map) {
+                final StringBuilder map = new StringBuilder("[");
+                for (final Map.Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) {
+                    map.append("(").
+                            append(convertToString(entry.getKey())).
+                            append("):(").
+                            append(convertToString(entry.getValue())).
+                            append("),");
+                }
+
+                // only need to remove this last bit if entries were added
+                if (!((Map<?, ?>) object).isEmpty())
+                    map.deleteCharAt(map.length() - 1);
+
+                return map.append("]").toString();
+            } else if (object instanceof Long)
+                return object + "L";
+            else if (object instanceof Double)
+                return object + "d";
+            else if (object instanceof Float)
+                return object + "f";
+            else if (object instanceof Integer)
+                return "(int) " + object;
+            else if (object instanceof Class)
+                return ((Class) object).getCanonicalName();
+            else if (object instanceof Timestamp)
+                return "new java.sql.Timestamp(" + ((Timestamp) object).getTime() + ")";
+            else if (object instanceof Date)
+                return "new java.util.Date(" + ((Date) object).getTime() + ")";
+            else if (object instanceof UUID)
+                return "java.util.UUID.fromString('" + object.toString() + "')";
+            else if (object instanceof P)
+                return convertPToString((P) object, new StringBuilder()).toString();
+            else if (object instanceof SackFunctions.Barrier)
+                return "SackFunctions.Barrier." + object.toString();
+            else if (object instanceof VertexProperty.Cardinality)
+                return "VertexProperty.Cardinality." + object.toString();
+            else if (object instanceof TraversalOptionParent.Pick)
+                return "TraversalOptionParent.Pick." + object.toString();
+            else if (object instanceof Enum)
+                return ((Enum) object).getDeclaringClass().getSimpleName() + "." + object.toString();
+            else if (object instanceof Element) {
+                if (object instanceof Vertex) {
+                    final Vertex vertex = (Vertex) object;
+                    return "new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex(" +
+                            convertToString(vertex.id()) + "," +
+                            convertToString(vertex.label()) + ", Collections.emptyMap())";
+                } else if (object instanceof Edge) {
+                    final Edge edge = (Edge) object;
+                    return "new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge(" +
+                            convertToString(edge.id()) + "," +
+                            convertToString(edge.label()) + "," +
+                            "Collections.emptyMap()," +
+                            convertToString(edge.outVertex().id()) + "," +
+                            convertToString(edge.outVertex().label()) + "," +
+                            convertToString(edge.inVertex().id()) + "," +
+                            convertToString(edge.inVertex().label()) + ")";
+                } else {// VertexProperty
+                    final VertexProperty vertexProperty = (VertexProperty) object;
+                    return "new org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertexProperty(" +
+                            convertToString(vertexProperty.id()) + "," +
+                            convertToString(vertexProperty.label()) + "," +
+                            convertToString(vertexProperty.value()) + "," +
+                            "Collections.emptyMap()," +
+                            convertToString(vertexProperty.element()) + ")";
+                }
+            } else if (object instanceof Lambda) {
+                final String lambdaString = ((Lambda) object).getLambdaScript().trim();
+                return lambdaString.startsWith("{") ? lambdaString : "{" + lambdaString + "}";
+            } else if (object instanceof TraversalStrategyProxy) {
+                final TraversalStrategyProxy proxy = (TraversalStrategyProxy) object;
+                if (proxy.getConfiguration().isEmpty())
+                    return proxy.getStrategyClass().getCanonicalName() + ".instance()";
+                else
+                    return proxy.getStrategyClass().getCanonicalName() + ".create(new org.apache.commons.configuration.MapConfiguration(" + convertToString(ConfigurationConverter.getMap(proxy.getConfiguration())) + "))";
+            } else if (object instanceof TraversalStrategy) {
+                return convertToString(new TraversalStrategyProxy(((TraversalStrategy) object)));
+            } else
+                return null == object ? "null" : object.toString();
+        }
+
+        private String internalTranslate(final String start, final Bytecode bytecode) {
+            final StringBuilder traversalScript = new StringBuilder(start);
+            for (final Bytecode.Instruction instruction : bytecode.getInstructions()) {
+                final String methodName = instruction.getOperator();
+                if (0 == instruction.getArguments().length)
+                    traversalScript.append(".").append(methodName).append("()");
+                else {
+                    traversalScript.append(".");
+                    String temp = methodName + "(";
+                    for (final Object object : instruction.getArguments()) {
+                        temp = temp + convertToString(object) + ",";
+                    }
+                    traversalScript.append(temp.substring(0, temp.length() - 1)).append(")");
+                }
             }
-            current.append(")");
-        } else
-            current.append("P.").append(p.getBiPredicate().toString()).append("(").append(convertToString(p.getValue())).append(")");
-        return current;
-    }
+            return traversalScript.toString();
+        }
 
-    private StringBuilder convertTextPToString(final TextP p, final StringBuilder current) {
-        current.append("TextP.").append(p.getBiPredicate().toString()).append("(").append(convertToString(p.getValue())).append(")");
-        return current;
+        private StringBuilder convertPToString(final P p, final StringBuilder current) {
+            if (p instanceof TextP) return convertTextPToString((TextP) p, current);
+            if (p instanceof ConnectiveP) {
+                final List<P<?>> list = ((ConnectiveP) p).getPredicates();
+                for (int i = 0; i < list.size(); i++) {
+                    convertPToString(list.get(i), current);
+                    if (i < list.size() - 1)
+                        current.append(p instanceof OrP ? ".or(" : ".and(");
+                }
+                current.append(")");
+            } else
+                current.append("P.").append(p.getBiPredicate().toString()).append("(").append(convertToString(p.getValue())).append(")");
+            return current;
+        }
+
+        private StringBuilder convertTextPToString(final TextP p, final StringBuilder current) {
+            current.append("TextP.").append(p.getBiPredicate().toString()).append("(").append(convertToString(p.getValue())).append(")");
+            return current;
+        }
     }
 }