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 2019/08/27 14:51:22 UTC

[tinkerpop] branch TINKERPOP-2291 created (now 12a8bdf)

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

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


      at 12a8bdf  TINKERPOP-2291 Added GraphSON support for deserialization of TraversalExplanation

This branch includes the following new commits:

     new 12a8bdf  TINKERPOP-2291 Added GraphSON support for deserialization of TraversalExplanation

The 1 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] 01/01: TINKERPOP-2291 Added GraphSON support for deserialization of TraversalExplanation

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

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

commit 12a8bdf6c73b45373eef129cd6bc5938e4ef4a51
Author: Stephen Mallette <sp...@genoprime.com>
AuthorDate: Tue Aug 27 10:47:48 2019 -0400

    TINKERPOP-2291 Added GraphSON support for deserialization of TraversalExplanation
    
    Added the notion of ImmutableExplanation which allows the deserialization to occur. As an immutable object the recalculation of the TraversalExplanation does not need to occur from the raw data. In fact, if it did it would be "wrong" as it would recalculate on the client rather than the server and server side strategies may not be applied. That said, the design choices here in the class hierarchy are a bit odd in that ImmutableExplanation should really extend AbstractExplanation, but  [...]
---
 CHANGELOG.asciidoc                                 |   6 +-
 ...alExplanation.java => AbstractExplanation.java} | 104 +++++++------------
 .../traversal/util/ImmutableExplanation.java       |  80 +++++++++++++++
 .../traversal/util/TraversalExplanation.java       | 112 ++++-----------------
 .../structure/io/graphson/GraphSONModule.java      |   2 +
 .../io/graphson/GraphSONSerializersV2d0.java       |  28 ++++++
 .../io/graphson/GraphSONSerializersV3d0.java       |  28 ++++++
 .../traversal/util/TraversalExplanationTest.java   |   8 ++
 .../graphson/GraphSONMapperEmbeddedTypeTest.java   |  11 ++
 9 files changed, 217 insertions(+), 162 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 32aa1ee..7705a17 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -23,8 +23,10 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 [[release-3-3-9]]
 === TinkerPop 3.3.9 (Release Date: NOT OFFICIALLY RELEASED YET)
 
-* Update jackson databind 2.9.9.3.
-* Postpone the timing of transport creation to `connection.write` in Gremlin Python.
+* Added `ImmutableExplanation` for a `TraversalExplanation` that just contains data.
+* Fixed `TraversalExplanation` deserialization in GraphSON 2 and 3 which was not supported before in Java.
+* Updated jackson databind 2.9.9.3.
+* Postponed the timing of transport creation to `connection.write` in Gremlin Python.
 
 [[release-3-3-8]]
 === TinkerPop 3.3.8 (Release Date: August 5, 2019)
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalExplanation.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/AbstractExplanation.java
similarity index 51%
copy from gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalExplanation.java
copy to gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/AbstractExplanation.java
index 6e3692a..ffb4108 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalExplanation.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/AbstractExplanation.java
@@ -16,67 +16,36 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
 package org.apache.tinkerpop.gremlin.process.traversal.util;
 
-import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
-import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies;
-import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
-import org.javatuples.Pair;
+import org.javatuples.Triplet;
 
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 /**
- * A TraversalExplanation takes a {@link Traversal} and, for each registered {@link TraversalStrategy}, it creates a
- * mapping reflecting how each strategy alters the traversal. This is useful for understanding how each traversal
- * strategy mutates the traversal. This is useful in debugging and analysis of traversal compilation. The
- * {@link TraversalExplanation#toString()} has a pretty-print representation that is useful in the Gremlin Console.
- *
- * @author Marko A. Rodriguez (http://markorodriguez.com)
+ * Base class for "TraversalExplanation" instances and centralizes the key functionality which is the job of doing
+ * {@link #prettyPrint()}.
+ * 
+ * @author Stephen Mallette (http://stephen.genoprime.com)
  */
-public class TraversalExplanation implements Serializable {
-
-    private Traversal.Admin<?, ?> traversal;
-    private List<Pair<TraversalStrategy, Traversal.Admin<?, ?>>> strategyTraversals = new ArrayList<>();
+public abstract class AbstractExplanation {
 
-    private TraversalExplanation() {
-        // no arg constructor for serialization
-    }
+    protected abstract Stream<String> getStrategyTraversalsAsString();
 
-    public TraversalExplanation(final Traversal.Admin<?, ?> traversal) {
-        this.traversal = traversal.clone();
-        TraversalStrategies mutatingStrategies = new DefaultTraversalStrategies();
-        for (final TraversalStrategy strategy : this.traversal.getStrategies().toList()) {
-            final Traversal.Admin<?, ?> mutatingTraversal = this.traversal.clone();
-            mutatingStrategies.addStrategies(strategy);
-            mutatingTraversal.setStrategies(mutatingStrategies);
-            mutatingTraversal.applyStrategies();
-            this.strategyTraversals.add(Pair.with(strategy, mutatingTraversal));
-        }
+    protected Stream<String> getTraversalStepsAsString() {
+        return Stream.concat(Stream.of(this.getOriginalTraversalAsString()), getIntermediates().map(Triplet::getValue2));
     }
 
-    /**
-     * Get the list of {@link TraversalStrategy} applications. For strategy, the resultant mutated {@link Traversal} is provided.
-     *
-     * @return the list of strategy/traversal pairs
-     */
-    public List<Pair<TraversalStrategy, Traversal.Admin<?, ?>>> getStrategyTraversals() {
-        return Collections.unmodifiableList(this.strategyTraversals);
-    }
+    protected abstract String getOriginalTraversalAsString();
 
     /**
-     * Get the original {@link Traversal} used to create this explanation.
-     *
-     * @return the original traversal
+     * First string is the traversal strategy, the second is the category and the third is the traversal
+     * representation at that point.
      */
-    public Traversal.Admin<?, ?> getOriginalTraversal() {
-        return this.traversal;
-    }
+    protected abstract Stream<Triplet<String,String,String>> getIntermediates();
 
     @Override
     public String toString() {
@@ -95,9 +64,7 @@ public class TraversalExplanation implements Serializable {
     public String prettyPrint(final int maxLineLength) {
         final String originalTraversal = "Original Traversal";
         final String finalTraversal = "Final Traversal";
-        final int maxStrategyColumnLength = this.strategyTraversals.stream()
-                .map(Pair::getValue0)
-                .map(Object::toString)
+        final int maxStrategyColumnLength = getStrategyTraversalsAsString()
                 .map(String::length)
                 .max(Comparator.naturalOrder())
                 .orElse(15);
@@ -105,9 +72,7 @@ public class TraversalExplanation implements Serializable {
         final int maxTraversalColumn = maxLineLength - newLineIndent;
         if (maxTraversalColumn < 1)
             throw new IllegalArgumentException("The maximum line length is too small to present the " + TraversalExplanation.class.getSimpleName() + ": " + maxLineLength);
-        int largestTraversalColumn = Stream.concat(Stream.of(Pair.with(null, this.traversal)), this.strategyTraversals.stream())
-                .map(Pair::getValue1)
-                .map(Object::toString)
+        int largestTraversalColumn = getTraversalStepsAsString()
                 .map(s -> wordWrap(s, maxTraversalColumn, newLineIndent))
                 .flatMap(s -> Stream.of(s.split("\n")))
                 .map(String::trim)
@@ -120,34 +85,40 @@ public class TraversalExplanation implements Serializable {
         for (int i = 0; i < (maxStrategyColumnLength + 7 + largestTraversalColumn); i++) {
             builder.append("=");
         }
-        builder.append("\n");
-        builder.append(originalTraversal);
-        for (int i = 0; i < maxStrategyColumnLength - originalTraversal.length() + 7; i++) {
-            builder.append(" ");
-        }
-        builder.append(wordWrap(this.traversal.toString(), maxTraversalColumn, newLineIndent));
+
+        spacing(originalTraversal, maxStrategyColumnLength, builder);
+
+        builder.append(wordWrap(getOriginalTraversalAsString(), maxTraversalColumn, newLineIndent));
         builder.append("\n\n");
-        for (final Pair<TraversalStrategy, Traversal.Admin<?, ?>> pairs : this.strategyTraversals) {
-            builder.append(pairs.getValue0());
-            int spacesToAdd = maxStrategyColumnLength - pairs.getValue0().toString().length() + 1;
+
+        final List<Triplet<String,String,String>> intermediates = this.getIntermediates().collect(Collectors.toList());
+        for (Triplet<String,String,String> t : intermediates) {
+            builder.append(t.getValue0());
+            int spacesToAdd = maxStrategyColumnLength - t.getValue0().length() + 1;
             for (int i = 0; i < spacesToAdd; i++) {
                 builder.append(" ");
             }
-            builder.append("[").append(pairs.getValue0().getTraversalCategory().getSimpleName().substring(0, 1)).append("]");
+            builder.append("[").append(t.getValue1().substring(0, 1)).append("]");
             for (int i = 0; i < 3; i++) {
                 builder.append(" ");
             }
-            builder.append(wordWrap(pairs.getValue1().toString(), maxTraversalColumn, newLineIndent)).append("\n");
+            builder.append(wordWrap(t.getValue2(), maxTraversalColumn, newLineIndent)).append("\n");
         }
+
+        spacing(finalTraversal, maxStrategyColumnLength, builder);
+
+        builder.append(wordWrap((intermediates.size() > 0 ?
+                intermediates.get(intermediates.size() - 1).getValue2() :
+                getOriginalTraversalAsString()), maxTraversalColumn, newLineIndent));
+        return builder.toString();
+    }
+
+    public static void spacing(String finalTraversal, int maxStrategyColumnLength, StringBuilder builder) {
         builder.append("\n");
         builder.append(finalTraversal);
         for (int i = 0; i < maxStrategyColumnLength - finalTraversal.length() + 7; i++) {
             builder.append(" ");
         }
-        builder.append(wordWrap((this.strategyTraversals.size() > 0 ?
-                this.strategyTraversals.get(this.strategyTraversals.size() - 1).getValue1().toString() :
-                this.traversal.toString()), maxTraversalColumn, newLineIndent));
-        return builder.toString();
     }
 
     private String wordWrap(final String longString, final int maxLengthPerLine, final int newLineIndent) {
@@ -174,5 +145,4 @@ public class TraversalExplanation implements Serializable {
 
         return builder.toString();
     }
-
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ImmutableExplanation.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ImmutableExplanation.java
new file mode 100644
index 0000000..2929319
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ImmutableExplanation.java
@@ -0,0 +1,80 @@
+/*
+ * 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.util;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
+import org.javatuples.Pair;
+import org.javatuples.Triplet;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+/**
+ * A data-only representation of a {@link TraversalExplanation} which doesn't re-calculate the "explanation" from
+ * the raw traversal data each time the explanation is displayed.
+ *
+ * @author Stephen Mallette (http://stephen.genoprime.com)
+ */
+public class ImmutableExplanation extends TraversalExplanation {
+
+    private final String originalTraversal;
+    private final List<Triplet<String, String, String>> intermediates;
+
+    public ImmutableExplanation(final String originalTraversal,
+                                final List<Triplet<String, String, String>> intermediates) {
+        this.originalTraversal = originalTraversal;
+        this.intermediates = intermediates;
+    }
+
+    @Override
+    public List<Pair<TraversalStrategy, Traversal.Admin<?, ?>>> getStrategyTraversals() {
+        throw new UnsupportedOperationException("This instance is immutable");
+    }
+
+    @Override
+    public Traversal.Admin<?, ?> getOriginalTraversal() {
+        throw new UnsupportedOperationException("This instance is immutable");
+    }
+
+    @Override
+    public ImmutableExplanation asImmutable() {
+        return this;
+    }
+
+    @Override
+    protected Stream<String> getStrategyTraversalsAsString() {
+        return getIntermediates().map(Triplet::getValue0);
+    }
+
+    @Override
+    protected Stream<String> getTraversalStepsAsString() {
+        return Stream.concat(Stream.of(this.originalTraversal), getIntermediates().map(Triplet::getValue2));
+    }
+
+    @Override
+    protected String getOriginalTraversalAsString() {
+        return this.originalTraversal;
+    }
+
+    @Override
+    protected Stream<Triplet<String, String, String>> getIntermediates() {
+        return intermediates.stream();
+    }
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalExplanation.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalExplanation.java
index 6e3692a..c4fa057 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalExplanation.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalExplanation.java
@@ -23,12 +23,13 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
 import org.javatuples.Pair;
+import org.javatuples.Triplet;
 
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 /**
@@ -39,12 +40,12 @@ import java.util.stream.Stream;
  *
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public class TraversalExplanation implements Serializable {
+public class TraversalExplanation extends AbstractExplanation implements Serializable {
 
     private Traversal.Admin<?, ?> traversal;
     private List<Pair<TraversalStrategy, Traversal.Admin<?, ?>>> strategyTraversals = new ArrayList<>();
 
-    private TraversalExplanation() {
+    protected TraversalExplanation() {
         // no arg constructor for serialization
     }
 
@@ -78,101 +79,26 @@ public class TraversalExplanation implements Serializable {
         return this.traversal;
     }
 
-    @Override
-    public String toString() {
-        return this.prettyPrint(Integer.MAX_VALUE);
-    }
-
-    public String prettyPrint() {
-        return this.prettyPrint(100);
+    public ImmutableExplanation asImmutable() {
+        return new ImmutableExplanation(getOriginalTraversalAsString(),
+                this.getIntermediates().collect(Collectors.toList()));
     }
 
-    /**
-     * A pretty-print representation of the traversal explanation.
-     *
-     * @return a {@link String} representation of the traversal explanation
-     */
-    public String prettyPrint(final int maxLineLength) {
-        final String originalTraversal = "Original Traversal";
-        final String finalTraversal = "Final Traversal";
-        final int maxStrategyColumnLength = this.strategyTraversals.stream()
+    @Override
+    protected Stream<String> getStrategyTraversalsAsString() {
+        return this.strategyTraversals.stream()
                 .map(Pair::getValue0)
-                .map(Object::toString)
-                .map(String::length)
-                .max(Comparator.naturalOrder())
-                .orElse(15);
-        final int newLineIndent = maxStrategyColumnLength + 10;
-        final int maxTraversalColumn = maxLineLength - newLineIndent;
-        if (maxTraversalColumn < 1)
-            throw new IllegalArgumentException("The maximum line length is too small to present the " + TraversalExplanation.class.getSimpleName() + ": " + maxLineLength);
-        int largestTraversalColumn = Stream.concat(Stream.of(Pair.with(null, this.traversal)), this.strategyTraversals.stream())
-                .map(Pair::getValue1)
-                .map(Object::toString)
-                .map(s -> wordWrap(s, maxTraversalColumn, newLineIndent))
-                .flatMap(s -> Stream.of(s.split("\n")))
-                .map(String::trim)
-                .map(s -> s.trim().startsWith("[") ? s : "   " + s) // 3 indent on new lines
-                .map(String::length)
-                .max(Comparator.naturalOrder())
-                .get();
-
-        final StringBuilder builder = new StringBuilder("Traversal Explanation\n");
-        for (int i = 0; i < (maxStrategyColumnLength + 7 + largestTraversalColumn); i++) {
-            builder.append("=");
-        }
-        builder.append("\n");
-        builder.append(originalTraversal);
-        for (int i = 0; i < maxStrategyColumnLength - originalTraversal.length() + 7; i++) {
-            builder.append(" ");
-        }
-        builder.append(wordWrap(this.traversal.toString(), maxTraversalColumn, newLineIndent));
-        builder.append("\n\n");
-        for (final Pair<TraversalStrategy, Traversal.Admin<?, ?>> pairs : this.strategyTraversals) {
-            builder.append(pairs.getValue0());
-            int spacesToAdd = maxStrategyColumnLength - pairs.getValue0().toString().length() + 1;
-            for (int i = 0; i < spacesToAdd; i++) {
-                builder.append(" ");
-            }
-            builder.append("[").append(pairs.getValue0().getTraversalCategory().getSimpleName().substring(0, 1)).append("]");
-            for (int i = 0; i < 3; i++) {
-                builder.append(" ");
-            }
-            builder.append(wordWrap(pairs.getValue1().toString(), maxTraversalColumn, newLineIndent)).append("\n");
-        }
-        builder.append("\n");
-        builder.append(finalTraversal);
-        for (int i = 0; i < maxStrategyColumnLength - finalTraversal.length() + 7; i++) {
-            builder.append(" ");
-        }
-        builder.append(wordWrap((this.strategyTraversals.size() > 0 ?
-                this.strategyTraversals.get(this.strategyTraversals.size() - 1).getValue1().toString() :
-                this.traversal.toString()), maxTraversalColumn, newLineIndent));
-        return builder.toString();
+                .map(Object::toString);
     }
 
-    private String wordWrap(final String longString, final int maxLengthPerLine, final int newLineIndent) {
-        if (longString.length() <= maxLengthPerLine)
-            return longString;
-
-        StringBuilder builder = new StringBuilder();
-        int counter = 0;
-        for (int i = 0; i < longString.length(); i++) {
-            if (0 == counter) {
-                builder.append(longString.charAt(i));
-            } else if (counter < maxLengthPerLine) {
-                builder.append(longString.charAt(i));
-            } else {
-                builder.append("\n");
-                for (int j = 0; j < newLineIndent; j++) {
-                    builder.append(" ");
-                }
-                builder.append(longString.charAt(i));
-                counter = 0;
-            }
-            counter++;
-        }
-
-        return builder.toString();
+    @Override
+    protected String getOriginalTraversalAsString() {
+        return getOriginalTraversal().toString();
     }
 
+    @Override
+    protected Stream<Triplet<String, String, String>> getIntermediates() {
+        return this.strategyTraversals.stream().map( p -> Triplet.with(p.getValue0().toString(),
+                p.getValue0().getTraversalCategory().getSimpleName(), p.getValue1().toString()));
+    }
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
index 3bbea66..75b2c5e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
@@ -247,6 +247,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
             addDeserializer(Edge.class, new GraphSONSerializersV3d0.EdgeJacksonDeserializer());
             addDeserializer(Property.class, new GraphSONSerializersV3d0.PropertyJacksonDeserializer());
             addDeserializer(Path.class, new GraphSONSerializersV3d0.PathJacksonDeserializer());
+            addDeserializer(TraversalExplanation.class, new GraphSONSerializersV3d0.TraversalExplanationJacksonDeserializer());
             addDeserializer(VertexProperty.class, new GraphSONSerializersV3d0.VertexPropertyJacksonDeserializer());
             addDeserializer(Metrics.class, new GraphSONSerializersV3d0.MetricsJacksonDeserializer());
             addDeserializer(TraversalMetrics.class, new GraphSONSerializersV3d0.TraversalMetricsJacksonDeserializer());
@@ -464,6 +465,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
             addDeserializer(Property.class, new GraphSONSerializersV2d0.PropertyJacksonDeserializer());
             addDeserializer(Path.class, new GraphSONSerializersV2d0.PathJacksonDeserializer());
             addDeserializer(VertexProperty.class, new GraphSONSerializersV2d0.VertexPropertyJacksonDeserializer());
+            addDeserializer(TraversalExplanation.class, new GraphSONSerializersV2d0.TraversalExplanationJacksonDeserializer());
             addDeserializer(Metrics.class, new GraphSONSerializersV2d0.MetricsJacksonDeserializer());
             addDeserializer(TraversalMetrics.class, new GraphSONSerializersV2d0.TraversalMetricsJacksonDeserializer());
             addDeserializer(Tree.class, new GraphSONSerializersV2d0.TreeJacksonDeserializer());
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONSerializersV2d0.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONSerializersV2d0.java
index 6638853..0cb6e69 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONSerializersV2d0.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONSerializersV2d0.java
@@ -24,6 +24,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.MutablePath;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
 import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalMetrics;
+import org.apache.tinkerpop.gremlin.process.traversal.util.ImmutableExplanation;
 import org.apache.tinkerpop.gremlin.process.traversal.util.Metrics;
 import org.apache.tinkerpop.gremlin.process.traversal.util.MutableMetrics;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalExplanation;
@@ -57,6 +58,7 @@ import org.apache.tinkerpop.shaded.jackson.databind.ser.std.StdScalarSerializer;
 import org.apache.tinkerpop.shaded.jackson.databind.ser.std.StdSerializer;
 import org.apache.tinkerpop.shaded.jackson.databind.type.TypeFactory;
 import org.javatuples.Pair;
+import org.javatuples.Triplet;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -610,6 +612,32 @@ class GraphSONSerializersV2d0 {
         }
     }
 
+    static class TraversalExplanationJacksonDeserializer extends StdDeserializer<TraversalExplanation> {
+        public TraversalExplanationJacksonDeserializer() {
+            super(TraversalExplanation.class);
+        }
+
+        @Override
+        public TraversalExplanation deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
+            final Map<String, Object> explainData = deserializationContext.readValue(jsonParser, Map.class);
+            final String originalTraversal = explainData.get(GraphSONTokens.ORIGINAL).toString();
+            final List<Triplet<String, String, String>> intermediates = new ArrayList<>();
+            final List<Map<String,Object>> listMap = (List<Map<String,Object>>) explainData.get(GraphSONTokens.INTERMEDIATE);
+            for (Map<String,Object> m : listMap) {
+                intermediates.add(Triplet.with(m.get(GraphSONTokens.STRATEGY).toString(),
+                        m.get(GraphSONTokens.CATEGORY).toString(),
+                        m.get(GraphSONTokens.TRAVERSAL).toString()));
+            }
+
+            return new ImmutableExplanation(originalTraversal, intermediates);
+        }
+
+        @Override
+        public boolean isCachable() {
+            return true;
+        }
+    }
+
     static class MetricsJacksonDeserializer extends AbstractObjectDeserializer<Metrics> {
         public MetricsJacksonDeserializer() {
             super(Metrics.class);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONSerializersV3d0.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONSerializersV3d0.java
index 0cc4ead..a1b8d0b 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONSerializersV3d0.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONSerializersV3d0.java
@@ -24,6 +24,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.MutablePath;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
 import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalMetrics;
+import org.apache.tinkerpop.gremlin.process.traversal.util.ImmutableExplanation;
 import org.apache.tinkerpop.gremlin.process.traversal.util.Metrics;
 import org.apache.tinkerpop.gremlin.process.traversal.util.MutableMetrics;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalExplanation;
@@ -55,6 +56,7 @@ import org.apache.tinkerpop.shaded.jackson.databind.ser.std.StdScalarSerializer;
 import org.apache.tinkerpop.shaded.jackson.databind.ser.std.StdSerializer;
 import org.apache.tinkerpop.shaded.jackson.databind.type.TypeFactory;
 import org.javatuples.Pair;
+import org.javatuples.Triplet;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -613,6 +615,32 @@ class GraphSONSerializersV3d0 {
         }
     }
 
+    static class TraversalExplanationJacksonDeserializer extends StdDeserializer<TraversalExplanation> {
+        public TraversalExplanationJacksonDeserializer() {
+            super(TraversalExplanation.class);
+        }
+
+        @Override
+        public TraversalExplanation deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
+            final Map<String, Object> explainData = deserializationContext.readValue(jsonParser, Map.class);
+            final String originalTraversal = explainData.get(GraphSONTokens.ORIGINAL).toString();
+            final List<Triplet<String, String, String>> intermediates = new ArrayList<>();
+            final List<Map<String,Object>> listMap = (List<Map<String,Object>>) explainData.get(GraphSONTokens.INTERMEDIATE);
+            for (Map<String,Object> m : listMap) {
+                intermediates.add(Triplet.with(m.get(GraphSONTokens.STRATEGY).toString(),
+                        m.get(GraphSONTokens.CATEGORY).toString(),
+                        m.get(GraphSONTokens.TRAVERSAL).toString()));
+            }
+
+            return new ImmutableExplanation(originalTraversal, intermediates);
+        }
+
+        @Override
+        public boolean isCachable() {
+            return true;
+        }
+    }
+
     static class MetricsJacksonDeserializer extends StdDeserializer<Metrics> {
         public MetricsJacksonDeserializer() {
             super(Metrics.class);
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalExplanationTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalExplanationTest.java
index c7eab92..0a81238 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalExplanationTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalExplanationTest.java
@@ -39,6 +39,14 @@ import static org.junit.Assert.assertTrue;
 public class TraversalExplanationTest {
 
     @Test
+    public void shouldMakeImmutable() {
+        final TraversalExplanation explanation = __.V().out().out().explain();
+        final ImmutableExplanation immutable = explanation.asImmutable();
+        assertEquals(explanation.prettyPrint(), immutable.prettyPrint());
+        assertEquals(explanation.toString(), immutable.toString());
+    }
+
+    @Test
     public void shouldSupportAnonymousTraversals() {
         final String toString = __.out("knows").in("created").explain().toString();
         assertTrue(toString.contains("Traversal Explanation"));
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapperEmbeddedTypeTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapperEmbeddedTypeTest.java
index 23d727c..6c27770 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapperEmbeddedTypeTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapperEmbeddedTypeTest.java
@@ -22,6 +22,7 @@ import org.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraver
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalExplanation;
 import org.apache.tinkerpop.gremlin.util.function.Lambda;
 import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
 import org.junit.Test;
@@ -51,6 +52,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.__;
 import static org.hamcrest.Matchers.either;
 import static org.hamcrest.core.IsNot.not;
 import static org.hamcrest.core.StringStartsWith.startsWith;
@@ -84,6 +86,15 @@ public class GraphSONMapperEmbeddedTypeTest extends AbstractGraphSONTest {
     public String version;
 
     @Test
+    public void shouldHandleTraversalExplanation() throws Exception {
+        assumeThat(version, not(startsWith("v1")));
+        
+        final TraversalExplanation o = __().out().outV().outE().explain();
+        final TraversalExplanation deser = serializeDeserialize(mapper, o, TraversalExplanation.class);
+        assertEquals(o.prettyPrint(), deser.prettyPrint());
+    }
+
+    @Test
     public void shouldHandleNumberConstants() throws Exception {
         assumeThat(version, not(startsWith("v1")));