You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by ok...@apache.org on 2015/05/16 01:14:12 UTC

[05/10] incubator-tinkerpop git commit: TINKERPOP3-670: Add 'tail' step

TINKERPOP3-670: Add 'tail' step


Project: http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/commit/45c8c76d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/tree/45c8c76d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/diff/45c8c76d

Branch: refs/heads/master
Commit: 45c8c76dd33f0b28f01da7d96facd46de6b51a5b
Parents: 8963c3a
Author: mhfrantz <mf...@redsealnetworks.com>
Authored: Thu May 7 18:29:22 2015 -0700
Committer: mhfrantz <mf...@redsealnetworks.com>
Committed: Fri May 15 14:14:45 2015 -0700

----------------------------------------------------------------------
 .../traversal/dsl/graph/GraphTraversal.java     |  12 ++
 .../traversal/step/filter/TailGlobalStep.java   |  82 ++++++++
 .../traversal/step/map/RangeLocalStep.java      |  29 ++-
 .../traversal/step/map/TailLocalStep.java       |  81 ++++++++
 .../traversal/step/filter/GroovyTailTest.groovy |  80 ++++++++
 .../process/GroovyProcessComputerSuite.java     |   1 +
 .../process/GroovyProcessStandardSuite.java     |   2 +
 .../gremlin/process/ProcessComputerSuite.java   |   2 +
 .../gremlin/process/ProcessStandardSuite.java   |   3 +
 .../process/traversal/step/filter/TailTest.java | 198 +++++++++++++++++++
 10 files changed, 485 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/45c8c76d/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
----------------------------------------------------------------------
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
index 9139263..fa1e092 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
@@ -53,6 +53,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.filter.OrStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeGlobalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.filter.SampleGlobalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.filter.SimplePathStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.filter.TailGlobalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.filter.TimeLimitStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.filter.WhereStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeByPathStep;
@@ -91,6 +92,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectOneStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.SumGlobalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.SumLocalStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.TailLocalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.TreeStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.UnfoldStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.VertexStep;
@@ -548,6 +550,16 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
         return this.range(scope, 0, limit);
     }
 
+    public default GraphTraversal<S, E> tail(final long limit) {
+        return this.tail(Scope.global, limit);
+    }
+
+    public default GraphTraversal<S, E> tail(final Scope scope, final long limit) {
+        return this.asAdmin().addStep(scope.equals(Scope.global)
+                ? new TailGlobalStep<>(this.asAdmin(), limit)
+                : new TailLocalStep<>(this.asAdmin(), limit));
+    }
+
     public default GraphTraversal<S, E> simplePath() {
         return this.asAdmin().addStep(new SimplePathStep<>(this.asAdmin()));
     }

http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/45c8c76d/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/TailGlobalStep.java
----------------------------------------------------------------------
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/TailGlobalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/TailGlobalStep.java
new file mode 100644
index 0000000..e4db2a1
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/TailGlobalStep.java
@@ -0,0 +1,82 @@
+/*
+ * 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.step.filter;
+
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.AbstractStep;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.Set;
+
+/**
+ * @author Matt Frantz (http://github.com/mhfrantz)
+ */
+public final class TailGlobalStep<S> extends AbstractStep<S, S> {
+
+    private final long limit;
+    private Deque<Traverser.Admin<S>> tail;
+
+    public TailGlobalStep(final Traversal.Admin traversal, final long limit) {
+        super(traversal);
+        this.limit = limit;
+        this.tail = new ArrayDeque<Traverser.Admin<S>>((int)limit);
+    }
+
+    @Override
+    public Traverser<S> processNextStart() {
+        if (this.starts.hasNext()) {
+            this.starts.forEachRemaining(this::addTail);
+        }
+        return this.tail.pop();
+    }
+
+    @Override
+    public void reset() {
+        super.reset();
+        this.tail.clear();
+    }
+
+    @Override
+    public String toString() {
+        return TraversalHelper.makeStepString(this, this.limit);
+    }
+
+    @Override
+    public TailGlobalStep<S> clone() {
+        final TailGlobalStep<S> clone = (TailGlobalStep<S>) super.clone();
+        clone.tail = new ArrayDeque<Traverser.Admin<S>>(this.tail);
+        return clone;
+    }
+
+    @Override
+    public Set<TraverserRequirement> getRequirements() {
+        return Collections.singleton(TraverserRequirement.BULK);
+    }
+
+    private void addTail(Traverser.Admin<S> start) {
+        if (this.tail.size() >= this.limit)
+            this.tail.pop();
+        this.tail.add(start);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/45c8c76d/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RangeLocalStep.java
----------------------------------------------------------------------
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RangeLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RangeLocalStep.java
index b3dad36..d0a93a8 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RangeLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RangeLocalStep.java
@@ -46,15 +46,34 @@ public final class RangeLocalStep<S> extends MapStep<S, S> {
     @Override
     protected S map(final Traverser.Admin<S> traverser) {
         final S start = traverser.get();
+        return applyRange(start, this.low, this.high);
+    }
+
+    /**
+     * Extracts the specified range of elements from a collection.
+     * Return type depends on dynamic type of start.
+     * <ul>
+     * <li>
+     * Map becomes Map (order-preserving)
+     * </li>
+     * <li>
+     * Set becomes Set (order-preserving)
+     * </li>
+     * <li>
+     * Other Collection types become List
+     * </li>
+     * </ul>
+     */
+    static <S> S applyRange(S start, long low, long high) {
         if (start instanceof Map) {
             final Map map = (Map) start;
-            final long capacity = (this.high != -1 ? this.high : map.size()) - this.low;
+            final long capacity = (high != -1 ? high : map.size()) - low;
             final Map result = new LinkedHashMap((int) Math.min(capacity, map.size()));
             long c = 0L;
             for (final Object obj : map.entrySet()) {
                 final Map.Entry entry = (Map.Entry) obj;
-                if (c >= this.low) {
-                    if (c < this.high || this.high == -1) {
+                if (c >= low) {
+                    if (c < high || high == -1) {
                         result.put(entry.getKey(), entry.getValue());
                     } else break;
                 }
@@ -66,8 +85,8 @@ public final class RangeLocalStep<S> extends MapStep<S, S> {
             final Collection result = (collection instanceof Set) ? new LinkedHashSet() : new LinkedList();
             long c = 0L;
             for (final Object item : collection) {
-                if (c >= this.low) {
-                    if (c < this.high || this.high == -1) {
+                if (c >= low) {
+                    if (c < high || high == -1) {
                         result.add(item);
                     } else break;
                 }

http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/45c8c76d/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TailLocalStep.java
----------------------------------------------------------------------
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TailLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TailLocalStep.java
new file mode 100644
index 0000000..e2d76c9
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TailLocalStep.java
@@ -0,0 +1,81 @@
+/*
+ * 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.step.map;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException;
+import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Matt Frantz (http://github.com/mhfrantz)
+ */
+public final class TailLocalStep<S> extends MapStep<S, S> {
+
+    private final long limit;
+
+    public TailLocalStep(final Traversal.Admin traversal, final long limit) {
+        super(traversal);
+        this.limit = limit;
+    }
+
+    @Override
+    protected S map(final Traverser.Admin<S> traverser) {
+        // We may consider optimizing the iteration of these containers using subtype-specific interfaces.  For
+        // example, we could use descendingIterator if we have a Deque.  But in general, we cannot reliably iterate a
+        // collection in reverse, so we use the range algorithm with dynamically computed boundaries.
+        final S start = traverser.get();
+        final long high =
+            start instanceof Map ? ((Map)start).size() :
+            start instanceof Collection ? ((Collection)start).size() :
+            this.limit;
+        final long low = high - this.limit;
+        final S result = RangeLocalStep.applyRange(start, low, high);
+
+        // If we are limiting a collection to a single item, then emit only that item.
+        if (1 == this.limit && result instanceof Collection) {
+            final Collection c = (Collection) result;
+            if (c.isEmpty()) {
+                // We have nothing to emit, so stop traversal.
+                throw FastNoSuchElementException.instance();
+            } else {
+                return (S) c.iterator().next();
+            }
+        } else {
+            return result;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return TraversalHelper.makeStepString(this, this.limit);
+    }
+
+    @Override
+    public Set<TraverserRequirement> getRequirements() {
+        return Collections.singleton(TraverserRequirement.OBJECT);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/45c8c76d/gremlin-groovy-test/src/main/groovy/org/apache/tinkerpop/gremlin/process/traversal/step/filter/GroovyTailTest.groovy
----------------------------------------------------------------------
diff --git a/gremlin-groovy-test/src/main/groovy/org/apache/tinkerpop/gremlin/process/traversal/step/filter/GroovyTailTest.groovy b/gremlin-groovy-test/src/main/groovy/org/apache/tinkerpop/gremlin/process/traversal/step/filter/GroovyTailTest.groovy
new file mode 100644
index 0000000..a9fde91
--- /dev/null
+++ b/gremlin-groovy-test/src/main/groovy/org/apache/tinkerpop/gremlin/process/traversal/step/filter/GroovyTailTest.groovy
@@ -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.step.filter
+
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal
+import org.apache.tinkerpop.gremlin.structure.T
+import org.apache.tinkerpop.gremlin.structure.Vertex
+
+import java.util.List
+import java.util.Map
+
+import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.limit
+import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.unfold
+import static org.apache.tinkerpop.gremlin.process.traversal.Scope.global
+import static org.apache.tinkerpop.gremlin.process.traversal.Scope.local
+
+/**
+ * @author Matt Frantz (http://github.com/mhfrantz)
+ */
+public abstract class GroovyTailTest {
+
+    public static class Traversals extends TailTest {
+
+        @Override
+        public Traversal<Vertex, Object> get_g_V_id_order_tailXglobal_2X() {
+            g.V.id.order.tail(global, 2)
+        }
+
+        @Override
+        public Traversal<Vertex, Object> get_g_V_id_order_tailX2X() {
+            g.V.id.order.tail(2)
+        }
+
+        @Override
+        public Traversal<Vertex, Object> get_g_V_id_order_tailX7X() {
+            g.V.id.order.tail(7)
+        }
+
+        @Override
+        public Traversal<Vertex, List<Object>> get_g_V_asXaX_out_asXaX_out_asXaX_selectXaX_byXunfold_id_foldX_tailXlocal_2X() {
+            g.V.as('a').out.as('a').out.as('a').select('a').by(unfold().id.fold).tail(local, 2)
+        }
+
+        @Override
+        public Traversal<Vertex, Object> get_g_V_asXaX_out_asXaX_out_asXaX_selectXaX_byXunfold_id_foldX_tailXlocal_1X() {
+            g.V.as('a').out.as('a').out.as('a').select('a').by(unfold().id.fold).tail(local, 1)
+        }
+
+        @Override
+        public Traversal<Vertex, Object> get_g_V_asXaX_out_asXaX_out_asXaX_selectXaX_byXlimitXlocal_0XX_tailXlocal_1X() {
+            g.V.as('a').out.as('a').out.as('a').select('a').by(limit(local, 0)).tail(local, 1)
+        }
+
+        @Override
+        public Traversal<Vertex, Map<String, Object>> get_g_V_asXaX_out_asXbX_out_asXcX_select_byXT_idX_tailXlocal_2X() {
+            g.V.as('a').out.as('b').out.as('c').select.by(T.id).tail(local, 2)
+        }
+
+        @Override
+        public Traversal<Vertex, Map<String, Object>> get_g_V_asXaX_out_asXbX_out_asXcX_select_byXT_idX_tailXlocal_1X() {
+            g.V.as('a').out.as('b').out.as('c').select.by(T.id).tail(local, 1)
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/45c8c76d/gremlin-groovy-test/src/main/java/org/apache/tinkerpop/gremlin/process/GroovyProcessComputerSuite.java
----------------------------------------------------------------------
diff --git a/gremlin-groovy-test/src/main/java/org/apache/tinkerpop/gremlin/process/GroovyProcessComputerSuite.java b/gremlin-groovy-test/src/main/java/org/apache/tinkerpop/gremlin/process/GroovyProcessComputerSuite.java
index a4ee9e7..ba6a419 100644
--- a/gremlin-groovy-test/src/main/java/org/apache/tinkerpop/gremlin/process/GroovyProcessComputerSuite.java
+++ b/gremlin-groovy-test/src/main/java/org/apache/tinkerpop/gremlin/process/GroovyProcessComputerSuite.java
@@ -112,6 +112,7 @@ public class GroovyProcessComputerSuite extends ProcessComputerSuite {
             GroovyRangeTest.Traversals.class,
             GroovySampleTest.Traversals.class,
             GroovySimplePathTest.Traversals.class,
+            GroovyTailTest.Traversals.class,
             GroovyWhereTest.Traversals.class,
 
             // map

http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/45c8c76d/gremlin-groovy-test/src/main/java/org/apache/tinkerpop/gremlin/process/GroovyProcessStandardSuite.java
----------------------------------------------------------------------
diff --git a/gremlin-groovy-test/src/main/java/org/apache/tinkerpop/gremlin/process/GroovyProcessStandardSuite.java b/gremlin-groovy-test/src/main/java/org/apache/tinkerpop/gremlin/process/GroovyProcessStandardSuite.java
index a12ee68..2d84dd4 100644
--- a/gremlin-groovy-test/src/main/java/org/apache/tinkerpop/gremlin/process/GroovyProcessStandardSuite.java
+++ b/gremlin-groovy-test/src/main/java/org/apache/tinkerpop/gremlin/process/GroovyProcessStandardSuite.java
@@ -41,6 +41,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.filter.GroovyOrTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.filter.GroovyRangeTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.filter.GroovySampleTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.filter.GroovySimplePathTest;
+import org.apache.tinkerpop.gremlin.process.traversal.step.filter.GroovyTailTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.filter.GroovyWhereTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.GroovyAddEdgeTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.GroovyAddVertexTest;
@@ -111,6 +112,7 @@ public class GroovyProcessStandardSuite extends ProcessStandardSuite {
             GroovyRangeTest.Traversals.class,
             GroovySampleTest.Traversals.class,
             GroovySimplePathTest.Traversals.class,
+            GroovyTailTest.Traversals.class,
             GroovyWhereTest.Traversals.class,
             // map
             GroovyAddEdgeTest.Traversals.class,

http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/45c8c76d/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessComputerSuite.java
----------------------------------------------------------------------
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessComputerSuite.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessComputerSuite.java
index 85a92d4..b46cc84 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessComputerSuite.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessComputerSuite.java
@@ -116,6 +116,7 @@ public class ProcessComputerSuite extends AbstractGremlinSuite {
             RangeTest.Traversals.class,
             SampleTest.Traversals.class,
             SimplePathTest.Traversals.class,
+            TailTest.Traversals.class,
             WhereTest.Traversals.class,
 
             // map
@@ -184,6 +185,7 @@ public class ProcessComputerSuite extends AbstractGremlinSuite {
             RangeTest.class,
             SampleTest.class,
             SimplePathTest.class,
+            TailTest.class,
             WhereTest.class,
 
             // map

http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/45c8c76d/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java
----------------------------------------------------------------------
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java
index 4fa12c1..1888761 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java
@@ -39,6 +39,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.filter.OrTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.filter.SampleTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.filter.SimplePathTest;
+import org.apache.tinkerpop.gremlin.process.traversal.step.filter.TailTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.filter.WhereTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexTest;
@@ -114,6 +115,7 @@ public class ProcessStandardSuite extends AbstractGremlinSuite {
             RangeTest.Traversals.class,
             SampleTest.Traversals.class,
             SimplePathTest.Traversals.class,
+            TailTest.Traversals.class,
             WhereTest.Traversals.class,
 
             // map
@@ -194,6 +196,7 @@ public class ProcessStandardSuite extends AbstractGremlinSuite {
             RangeTest.class,
             SampleTest.class,
             SimplePathTest.class,
+            TailTest.class,
             WhereTest.class,
 
             // map

http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/45c8c76d/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/TailTest.java
----------------------------------------------------------------------
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/TailTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/TailTest.java
new file mode 100644
index 0000000..26ed69d
--- /dev/null
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/TailTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.step.filter;
+
+import org.apache.tinkerpop.gremlin.LoadGraphWith;
+import org.apache.tinkerpop.gremlin.process.AbstractGremlinProcessTest;
+import org.apache.tinkerpop.gremlin.process.GremlinProcessRunner;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData.MODERN;
+import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.limit;
+import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.unfold;
+import static org.apache.tinkerpop.gremlin.process.traversal.Scope.global;
+import static org.apache.tinkerpop.gremlin.process.traversal.Scope.local;
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Original spec: https://issues.apache.org/jira/browse/TINKERPOP3-670
+ *
+ * @author Matt Frantz (http://github.com/mhfrantz)
+ */
+@RunWith(GremlinProcessRunner.class)
+public abstract class TailTest extends AbstractGremlinProcessTest {
+
+    public abstract Traversal<Vertex, Object> get_g_V_id_order_tailXglobal_2X();
+
+    public abstract Traversal<Vertex, Object> get_g_V_id_order_tailX2X();
+
+    public abstract Traversal<Vertex, Object> get_g_V_id_order_tailX7X();
+
+    public abstract Traversal<Vertex, List<Object>> get_g_V_asXaX_out_asXaX_out_asXaX_selectXaX_byXunfold_id_foldX_tailXlocal_2X();
+
+    public abstract Traversal<Vertex, Object> get_g_V_asXaX_out_asXaX_out_asXaX_selectXaX_byXunfold_id_foldX_tailXlocal_1X();
+
+    public abstract Traversal<Vertex, Object> get_g_V_asXaX_out_asXaX_out_asXaX_selectXaX_byXlimitXlocal_0XX_tailXlocal_1X();
+
+    public abstract Traversal<Vertex, Map<String, Object>> get_g_V_asXaX_out_asXbX_out_asXcX_select_byXT_idX_tailXlocal_2X();
+
+    public abstract Traversal<Vertex, Map<String, Object>> get_g_V_asXaX_out_asXbX_out_asXcX_select_byXT_idX_tailXlocal_1X();
+
+    /** Scenario: Global scope */
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_V_id_order_tailXglobal_2X() {
+        final Traversal<Vertex, Object> traversal = get_g_V_id_order_tailXglobal_2X();
+        printTraversalForm(traversal);
+        assertEquals(Arrays.asList(5, 6), traversal.toList());
+    }
+
+    /** Scenario: Default scope is global */
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_V_id_order_tailX2X() {
+        final Traversal<Vertex, Object> traversal = get_g_V_id_order_tailX2X();
+        printTraversalForm(traversal);
+        assertEquals(Arrays.asList(5, 6), traversal.toList());
+    }
+
+    /** Scenario: Global scope, not enough elements */
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_V_id_order_tailX7X() {
+        final Traversal<Vertex, Object> traversal = get_g_V_id_order_tailX7X();
+        printTraversalForm(traversal);
+        assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6), traversal.toList());
+    }
+
+    /** Scenario: Local scope, List input, N>1 */
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_V_asXaX_out_asXaX_out_asXaX_selectXaX_byXunfold_id_foldX_tailXlocal_2X() {
+        final Traversal<Vertex, List<Object>> traversal = get_g_V_asXaX_out_asXaX_out_asXaX_selectXaX_byXunfold_id_foldX_tailXlocal_2X();
+        printTraversalForm(traversal);
+        final Set<List<Object>> expected =
+            new HashSet(Arrays.asList(
+                            Arrays.asList(4, 5),
+                            Arrays.asList(4, 3)));
+        final Set<List<Object>> actual = new HashSet(traversal.toList());
+        assertEquals(expected, actual);
+    }
+
+    /** Scenario: Local scope, List input, N=1 */
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_V_asXaX_out_asXaX_out_asXaX_selectXaX_byXunfold_id_foldX_tailXlocal_1X() {
+        final Traversal<Vertex, Object> traversal = get_g_V_asXaX_out_asXaX_out_asXaX_selectXaX_byXunfold_id_foldX_tailXlocal_1X();
+        printTraversalForm(traversal);
+        final Set<Object> expected = new HashSet(Arrays.asList(5, 3));
+        final Set<Object> actual = new HashSet(traversal.toList());
+        assertEquals(expected, actual);
+    }
+
+    /** Scenario: Local scope, List input, N=1, empty input */
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_V_asXaX_out_asXaX_out_asXaX_selectXaX_byXlimitXlocal_0XX_tailXlocal_1X() {
+        final Traversal<Vertex, Object> traversal = get_g_V_asXaX_out_asXaX_out_asXaX_selectXaX_byXlimitXlocal_0XX_tailXlocal_1X();
+        printTraversalForm(traversal);
+        final Set<Object> expected = new HashSet();
+        final Set<Object> actual = new HashSet(traversal.toList());
+        assertEquals(expected, actual);
+    }
+
+    /** Scenario: Local scope, Map input, N>1 */
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_V_asXaX_out_asXbX_out_asXcX_select_byXT_idX_tailXlocal_2X() {
+        final Traversal<Vertex, Map<String, Object>> traversal = get_g_V_asXaX_out_asXbX_out_asXcX_select_byXT_idX_tailXlocal_2X();
+        printTraversalForm(traversal);
+        final Set<Map<String, Object>> expected = new HashSet(makeMapList(2,
+                 "b", 4, "c", 5,
+                 "b", 4, "c", 3));
+        final Set<Map<String, Object>> actual = new HashSet(traversal.toList());
+        assertEquals(expected, actual);
+    }
+
+    /** Scenario: Local scope, Map input, N=1 */
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_V_asXaX_out_asXbX_out_asXcX_select_byXT_idX_tailXlocal_1X() {
+        final Traversal<Vertex, Map<String, Object>> traversal = get_g_V_asXaX_out_asXbX_out_asXcX_select_byXT_idX_tailXlocal_1X();
+        printTraversalForm(traversal);
+        final Set<Map<String, Object>> expected = new HashSet(makeMapList(1,
+                 "c", 5,
+                 "c", 3));
+        final Set<Map<String, Object>> actual = new HashSet(traversal.toList());
+        assertEquals(expected, actual);
+    }
+
+    public static class Traversals extends TailTest {
+
+        @Override
+        public Traversal<Vertex, Object> get_g_V_id_order_tailXglobal_2X() {
+            return g.V().id().order().tail(global, 2);
+        }
+
+        @Override
+        public Traversal<Vertex, Object> get_g_V_id_order_tailX2X() {
+            return g.V().id().order().tail(2);
+        }
+
+        @Override
+        public Traversal<Vertex, Object> get_g_V_id_order_tailX7X() {
+            return g.V().id().order().tail(7);
+        }
+
+        @Override
+        public Traversal<Vertex, List<Object>> get_g_V_asXaX_out_asXaX_out_asXaX_selectXaX_byXunfold_id_foldX_tailXlocal_2X() {
+            return g.V().as("a").out().as("a").out().as("a").<List<Object>>select("a").by(unfold().id().fold()).tail(local, 2);
+        }
+
+        @Override
+        public Traversal<Vertex, Object> get_g_V_asXaX_out_asXaX_out_asXaX_selectXaX_byXunfold_id_foldX_tailXlocal_1X() {
+            return g.V().as("a").out().as("a").out().as("a").<Object>select("a").by(unfold().id().fold()).tail(local, 1);
+        }
+
+        @Override
+        public Traversal<Vertex, Object> get_g_V_asXaX_out_asXaX_out_asXaX_selectXaX_byXlimitXlocal_0XX_tailXlocal_1X() {
+            return g.V().as("a").out().as("a").out().as("a").<Object>select("a").by(limit(local, 0)).tail(local, 1);
+        }
+
+        @Override
+        public Traversal<Vertex, Map<String, Object>> get_g_V_asXaX_out_asXbX_out_asXcX_select_byXT_idX_tailXlocal_2X() {
+            return g.V().as("a").out().as("b").out().as("c").<Object>select().by(T.id).tail(local, 2);
+        }
+
+        @Override
+        public Traversal<Vertex, Map<String, Object>> get_g_V_asXaX_out_asXbX_out_asXcX_select_byXT_idX_tailXlocal_1X() {
+            return g.V().as("a").out().as("b").out().as("c").<Object>select().by(T.id).tail(local, 1);
+        }
+    }
+}