You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by dk...@apache.org on 2019/06/11 15:37:59 UTC

[tinkerpop] branch TINKERPOP-1084 updated (0398aaf -> e8bd5c1)

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

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


 discard 0398aaf  TINKERPOP-1084 Allow predicates and traversals to be used as options in `BranchStep`.
 discard 5ed38b3  TINKERPOP-1084 Allow predicates to be used as options in BranchSteps.
     add 2770193  gt/gte/lt/lte can throw CCE if object isn't a Comparable
     add d01926f  Merge branch 'pr-1120' into tp33
     add b430314  Fix up changelog after PR #1120 CTR
     add a7728b2  Add test infrastructure to check for iterator leak
     add 853371f  Fix iterator leaks in query processor
     add acc0bed  Reorder imports
     add 7db30be  Added provider documentation
     add 6feb363  Add changelog entry
     add f86c95a  Fix failing build due to merge issue
     add b2e0225  Change spelling on TinkerPop in docs
     add ded403f  Disable iterator leak check on some tests
     add 3939f88  obtain an itr using a graph
     add 0283a80  Merge branch 'pr-1118' into tp33
     add ed8ddcb  Fixed typo for math() step and division CTR
     add 7a18d92  TINKERPOP-2230 Fixed bug in match() step
     new 2bc081d  TINKERPOP-1084 Allow predicates to be used as options in BranchSteps.
     new e8bd5c1  TINKERPOP-1084 Allow predicates and traversals to be used as options in `BranchStep`.

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (0398aaf)
            \
             N -- N -- N   refs/heads/TINKERPOP-1084 (e8bd5c1)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

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.


Summary of changes:
 CHANGELOG.asciidoc                                 |  4 +
 docs/src/dev/provider/index.asciidoc               | 10 +++
 docs/src/reference/the-traversal.asciidoc          |  4 +-
 .../gremlin/process/traversal/Compare.java         | 44 +++++++---
 .../gremlin/process/traversal/Traversal.java       |  9 +++
 .../process/traversal/step/filter/NotStep.java     |  2 +-
 .../traversal/step/filter/RangeGlobalStep.java     |  4 +
 .../process/traversal/step/map/MatchStep.java      | 12 +++
 .../process/traversal/util/DefaultTraversal.java   |  5 ++
 .../process/traversal/util/TraversalUtil.java      | 49 ++++++++++--
 .../util/iterator/StoreIteratorCounter.java        | 54 +++++++++++++
 .../process/traversal/CompareExceptionTest.java    | 76 ++++++++++++++++++
 .../gremlin/process/traversal/CompareTest.java     | 90 ++++++++++++++-------
 .../filter/{OrStepTest.java => NotStepTest.java}   | 15 ++--
 gremlin-test/features/map/Match.feature            | 13 +++
 .../tinkerpop/gremlin/AbstractGremlinTest.java     | 22 ++++-
 ...ptEngineToTest.java => IgnoreIteratorLeak.java} | 16 ++--
 .../generator/CommunityGeneratorTest.java          | 33 +++++---
 .../generator/DistributionGeneratorTest.java       |  9 ++-
 .../process/computer/GraphComputerTest.java        |  2 +
 .../bulkloading/BulkLoaderVertexProgramTest.java   |  2 +
 .../traversal/TraversalInterruptionTest.java       |  2 +
 .../process/traversal/step/filter/FilterTest.java  | 11 +--
 .../process/traversal/step/filter/HasTest.java     |  2 +
 .../process/traversal/step/filter/WhereTest.java   |  2 +-
 .../process/traversal/step/map/MatchTest.java      | 19 +++++
 .../traversal/step/sideEffect/SubgraphTest.java    |  2 +
 .../decoration/SubgraphStrategyProcessTest.java    |  2 +
 .../tinkerpop/gremlin/structure/io/IoTest.java     |  7 +-
 .../traversal/step/sideEffect/TinkerGraphStep.java | 49 +++++++++---
 .../gremlin/tinkergraph/structure/TinkerGraph.java |  6 +-
 .../tinkergraph/structure/TinkerGraphIterator.java | 93 ++++++++++++++++++++++
 32 files changed, 564 insertions(+), 106 deletions(-)
 create mode 100644 gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/iterator/StoreIteratorCounter.java
 create mode 100644 gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareExceptionTest.java
 copy gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/{OrStepTest.java => NotStepTest.java} (84%)
 copy gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/{jsr223/ScriptEngineToTest.java => IgnoreIteratorLeak.java} (70%)
 create mode 100644 tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphIterator.java


[tinkerpop] 01/02: TINKERPOP-1084 Allow predicates to be used as options in BranchSteps.

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

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

commit 2bc081daf86548e245d2c918193581fb4c131411
Author: Daniel Kuppitz <da...@hotmail.com>
AuthorDate: Mon Jun 10 12:45:16 2019 -0700

    TINKERPOP-1084 Allow predicates to be used as options in BranchSteps.
---
 CHANGELOG.asciidoc                                 |   1 +
 .../process/traversal/step/branch/BranchStep.java  | 102 ++++++++++++++++-----
 .../process/traversal/step/ComplexTest.java        |  58 +++++++++---
 .../tinkergraph/structure/TinkerGraphPlayTest.java |  22 +++--
 4 files changed, 141 insertions(+), 42 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 03ba330..8b2cd62 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -30,6 +30,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 * Added test infrastructure to check for storage iterator leak.
 * Fixed multiple iterator leaks in query processor.
 * Fixed bug in `MatchStep` where the correct was not properly determined.
+* Allow predicates to be used as options in BranchSteps.
 
 [[release-3-3-7]]
 === TinkerPop 3.3.7 (Release Date: May 28, 2019)
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStep.java
index e35d51e..efc43cc 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStep.java
@@ -38,18 +38,23 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
+ * @author Daniel Kuppitz (http://gremlin.guru)
  */
 public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements TraversalOptionParent<M, S, E> {
 
     protected Traversal.Admin<S, M> branchTraversal;
     protected Map<Object, List<Traversal.Admin<S, E>>> traversalOptions = new HashMap<>();
+
     private boolean first = true;
-    private boolean hasBarrier = false;
+    private boolean hasBarrier;
+    private boolean hasPredicateOptions;
 
     public BranchStep(final Traversal.Admin traversal) {
         super(traversal);
@@ -61,7 +66,8 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
 
     @Override
     public void addGlobalChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption) {
-        final Object pickTokenKey = PickTokenKey.make(pickToken);
+        final Object pickTokenKey = makePickTokenKey(pickToken);
+        this.hasPredicateOptions |= pickTokenKey instanceof PredicateOption;
         if (this.traversalOptions.containsKey(pickTokenKey))
             this.traversalOptions.get(pickTokenKey).add(traversalOption);
         else
@@ -138,14 +144,13 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
     private void applyCurrentTraverser(final Traverser.Admin<S> start) {
         // first get the value of the choice based on the current traverser and use that to select the right traversal
         // option to which that traverser should be routed
-        final Object choice = PickTokenKey.make(TraversalUtil.apply(start, this.branchTraversal));
-        final List<Traversal.Admin<S, E>> branch = this.traversalOptions.containsKey(choice) ?
-                this.traversalOptions.get(choice) : this.traversalOptions.get(Pick.none);
+        final Object choice = makePickTokenKey(TraversalUtil.apply(start, this.branchTraversal));
+        final List<Traversal.Admin<S, E>> branches = pickBranches(choice);
 
         // if a branch is identified, then split the traverser and add it to the start of the option so that when
         // that option is iterated (in the calling method) that value can be applied.
-        if (null != branch)
-            branch.forEach(traversal -> traversal.addStart(start.split()));
+        if (null != branches)
+            branches.forEach(traversal -> traversal.addStart(start.split()));
 
         if (choice != Pick.any) {
             final List<Traversal.Admin<S, E>> anyBranch = this.traversalOptions.get(Pick.any);
@@ -158,10 +163,10 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
     protected Iterator<Traverser.Admin<E>> computerAlgorithm() {
         final List<Traverser.Admin<E>> ends = new ArrayList<>();
         final Traverser.Admin<S> start = this.starts.next();
-        final Object choice = PickTokenKey.make(TraversalUtil.apply(start, this.branchTraversal));
-        final List<Traversal.Admin<S, E>> branch = this.traversalOptions.containsKey(choice) ? this.traversalOptions.get(choice) : this.traversalOptions.get(Pick.none);
-        if (null != branch) {
-            branch.forEach(traversal -> {
+        final Object choice = makePickTokenKey(TraversalUtil.apply(start, this.branchTraversal));
+        final List<Traversal.Admin<S, E>> branches = pickBranches(choice);
+        if (null != branches) {
+            branches.forEach(traversal -> {
                 final Traverser.Admin<E> split = (Traverser.Admin<E>) start.split();
                 split.setStepId(traversal.getStartStep().getId());
                 //split.addLabels(this.labels);
@@ -182,6 +187,22 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
         return ends.iterator();
     }
 
+    private List<Traversal.Admin<S, E>> pickBranches(final Object choice) {
+        final List<Traversal.Admin<S, E>> branches = new ArrayList<>();
+        if (this.hasPredicateOptions) {
+            for (final Map.Entry<Object, List<Traversal.Admin<S, E>>> e : this.traversalOptions.entrySet()) {
+                if (Objects.equals(e.getKey(), choice)) {
+                    branches.addAll(e.getValue());
+                }
+            }
+        } else {
+            if (this.traversalOptions.containsKey(choice)) {
+                branches.addAll(this.traversalOptions.get(choice));
+            }
+        }
+        return branches.isEmpty() ? this.traversalOptions.get(Pick.none) : branches;
+    }
+
     @Override
     public BranchStep<S, E, M> clone() {
         final BranchStep<S, E, M> clone = (BranchStep<S, E, M>) super.clone();
@@ -230,26 +251,30 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
     }
 
     /**
-     * PickTokenKey is basically a wrapper for numbers that are used as a PickToken. This is
-     * required in order to treat equal numbers of different data types as a match.
+     * Converts numbers into {@link NumberOption} and predicates into {@link PredicateOption}.
+     */
+    private static Object makePickTokenKey(final Object o) {
+        return
+                o instanceof Number ? new NumberOption((Number) o) :
+                o instanceof Predicate ? new PredicateOption((Predicate) o) : o;
+    }
+
+    /**
+     * Wraps a single number and overrides equals/hashCode/compareTo in order to ignore the numeric data type.
      */
-    private static class PickTokenKey {
+    private static class NumberOption implements Comparable<Number> {
 
         final Number number;
 
-        private PickTokenKey(final Number number) {
+        NumberOption(final Number number) {
             this.number = number;
         }
 
-        static Object make(final Object value) {
-            return value instanceof Number ? new PickTokenKey((Number) value) : value;
-        }
-
         @Override
         public boolean equals(final Object o) {
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
-            final PickTokenKey other = (PickTokenKey) o;
+            final NumberOption other = (NumberOption) o;
             return 0 == NumberHelper.compare(number, other.number);
         }
 
@@ -262,5 +287,40 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
         public String toString() {
             return number.toString();
         }
+
+        @Override
+        public int compareTo(final Number other) {
+            return NumberHelper.compare(this.number, other);
+        }
+    }
+
+    /**
+     * Wraps a single predicate and overrides equals/hashCode so that the predicate can be matched against a concrete value.
+     */
+    private static class PredicateOption {
+
+        final Predicate predicate;
+
+        PredicateOption(final Predicate predicate) {
+            this.predicate = predicate;
+        }
+
+        @SuppressWarnings({"EqualsWhichDoesntCheckParameterClass", "unchecked"})
+        @Override
+        public boolean equals(final Object o) {
+            if (o instanceof PredicateOption)
+                return ((PredicateOption) o).predicate.equals(predicate);
+            return predicate.test(o);
+        }
+
+        @Override
+        public int hashCode() {
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return predicate.toString();
+        }
     }
-}
+}
\ No newline at end of file
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/ComplexTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/ComplexTest.java
index 75dd0f5..a3bb1cd 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/ComplexTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/ComplexTest.java
@@ -44,24 +44,14 @@ import java.util.Map;
 import static java.util.Collections.emptyList;
 import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData.MODERN;
 import static org.apache.tinkerpop.gremlin.process.traversal.P.eq;
+import static org.apache.tinkerpop.gremlin.process.traversal.P.gte;
 import static org.apache.tinkerpop.gremlin.process.traversal.P.lt;
 import static org.apache.tinkerpop.gremlin.process.traversal.P.neq;
 import static org.apache.tinkerpop.gremlin.process.traversal.Pop.all;
 import static org.apache.tinkerpop.gremlin.process.traversal.Pop.first;
 import static org.apache.tinkerpop.gremlin.process.traversal.Pop.last;
 import static org.apache.tinkerpop.gremlin.process.traversal.Scope.local;
-import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.both;
-import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.constant;
-import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.count;
-import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.group;
-import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.in;
-import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.out;
-import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.outE;
-import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.project;
-import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.select;
-import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.unfold;
-import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.values;
-import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.where;
+import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.*;
 import static org.apache.tinkerpop.gremlin.structure.Column.keys;
 import static org.apache.tinkerpop.gremlin.structure.Column.values;
 import static org.junit.Assert.assertEquals;
@@ -86,6 +76,8 @@ public abstract class ComplexTest extends AbstractGremlinProcessTest {
 
     public abstract Traversal<Vertex, Map<String, List<String>>> getPlaylistPaths();
 
+    public abstract Traversal<Vertex, Map<String, List<String>>> getAgeComments();
+
     /**
      * Checks the result of both coworkerSummary tests, which is expected to look as follows:
      * <p>
@@ -243,6 +235,31 @@ public abstract class ComplexTest extends AbstractGremlinProcessTest {
         assertTrue(map.get("artists").contains("Grateful_Dead"));
     }
 
+    @Test
+    @LoadGraphWith(LoadGraphWith.GraphData.MODERN)
+    public void ageComments() {
+        final Traversal<Vertex, Map<String, List<String>>> traversal = getAgeComments();
+        printTraversalForm(traversal);
+        assertTrue(traversal.hasNext());
+        Map<String, List<String>> map = traversal.next();
+        assertEquals(4, map.size());
+        assertTrue(map.containsKey("marko"));
+        assertEquals(2, map.get("marko").size());
+        assertTrue(map.get("marko").contains("almost old"));
+        assertTrue(map.get("marko").contains("younger than peter"));
+        assertTrue(map.containsKey("vadas"));
+        assertEquals(2, map.get("vadas").size());
+        assertTrue(map.get("vadas").contains("pretty young"));
+        assertTrue(map.get("vadas").contains("younger than peter"));
+        assertTrue(map.containsKey("josh"));
+        assertEquals(2, map.get("josh").size());
+        assertTrue(map.get("josh").contains("younger than peter"));
+        assertTrue(map.get("josh").contains("pretty old"));
+        assertTrue(map.containsKey("peter"));
+        assertEquals(1, map.get("peter").size());
+        assertTrue(map.get("peter").contains("pretty old"));
+    }
+
     public static class Traversals extends ComplexTest {
 
         @Override
@@ -265,7 +282,7 @@ public abstract class ComplexTest extends AbstractGremlinProcessTest {
         public Traversal<Vertex, Map<String, Map<String, Map<String, Object>>>> getCoworkerSummary() {
             return g.V().hasLabel("person").filter(outE("created")).aggregate("p").as("p1").values("name").as("p1n")
                     .select("p").unfold().where(neq("p1")).as("p2").values("name").as("p2n").select("p2")
-                    .out("created").choose(in("created").where(eq("p1")), values("name"), constant(emptyList()))
+                    .out("created").choose(in("created").where(eq("p1")), __.values("name"), constant(emptyList()))
                     .<String, Map<String, Map<String, Object>>>group().by(select("p1n")).
                             by(group().by(select("p2n")).
                                     by(unfold().fold().project("numCoCreated", "coCreated").by(count(local)).by()));
@@ -323,6 +340,17 @@ public abstract class ComplexTest extends AbstractGremlinProcessTest {
                     by("name").
                     by(__.coalesce(out("sungBy", "writtenBy").dedup().values("name"), constant("Unknown")).fold());
         }
-    }
-}
 
+        @Override
+        public Traversal<Vertex, Map<String, List<String>>> getAgeComments() {
+            return g.V().hasLabel("person")
+                    .<String, List<String>> group()
+                        .by("name")
+                        .by(branch(__.values("age"))
+                                .option(29, constant("almost old"))
+                                .option(lt(29), constant("pretty young"))
+                                .option(lt(35), constant("younger than peter"))
+                                .option(gte(30), constant("pretty old")).fold());
+        }
+    }
+}
\ No newline at end of file
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphPlayTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphPlayTest.java
index d4f7d5d..3b241bc 100644
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphPlayTest.java
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphPlayTest.java
@@ -27,6 +27,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.EarlyLimitStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.PathRetractionStrategy;
 import org.apache.tinkerpop.gremlin.structure.*;
@@ -45,6 +46,10 @@ import java.util.function.BiFunction;
 import java.util.function.Supplier;
 
 import static org.apache.tinkerpop.gremlin.process.traversal.Operator.sum;
+import static org.apache.tinkerpop.gremlin.process.traversal.P.between;
+import static org.apache.tinkerpop.gremlin.process.traversal.P.gt;
+import static org.apache.tinkerpop.gremlin.process.traversal.P.gte;
+import static org.apache.tinkerpop.gremlin.process.traversal.P.lt;
 import static org.apache.tinkerpop.gremlin.process.traversal.P.neq;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.*;
 
@@ -124,12 +129,17 @@ public class TinkerGraphPlayTest {
     @Ignore
     public void testPlayDK() throws Exception {
 
-        final GraphTraversalSource g = TinkerFactory.createModern().traversal();
-        g.V().match(
-                __.as("b").out("created").as("c"),
-                __.as("a").hasLabel("person"),
-                __.as("b").hasLabel("person"),
-                __.as("a").out("knows").as("b")).forEachRemaining(System.out::println);
+        GraphTraversalSource g = TinkerFactory.createModern().traversal();
+        g./*withComputer().*/V().hasLabel("person")
+                .project("name", "age", "comments")
+                    .by("name")
+                    .by("age")
+                    .by(branch(values("age"))
+                            .option(29, constant("almost old"))
+                            .option(lt(29), constant("pretty young"))
+                            .option(lt(35), constant("younger than peter"))
+                            .option(gte(30), constant("pretty old")).fold())
+                .forEachRemaining(System.out::println);
     }
 
     @Test


[tinkerpop] 02/02: TINKERPOP-1084 Allow predicates and traversals to be used as options in `BranchStep`.

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

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

commit e8bd5c1fc7058881b82858a4c88583c24b4260c9
Author: Daniel Kuppitz <da...@hotmail.com>
AuthorDate: Tue Jun 11 07:46:51 2019 -0700

    TINKERPOP-1084 Allow predicates and traversals to be used as options in `BranchStep`.
---
 CHANGELOG.asciidoc                                 |   2 +-
 .../traversal/lambda/PredicateTraversal.java       |  68 +++++++++
 .../process/traversal/step/branch/BranchStep.java  | 163 +++++++--------------
 .../process/traversal/step/branch/ChooseStep.java  |  10 +-
 .../process/traversal/step/branch/UnionStep.java   |   2 +-
 .../process/traversal/step/ComplexTest.java        |   4 +-
 .../tinkergraph/structure/TinkerGraphPlayTest.java |   8 +-
 7 files changed, 136 insertions(+), 121 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 8b2cd62..b181e4c 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -30,7 +30,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 * Added test infrastructure to check for storage iterator leak.
 * Fixed multiple iterator leaks in query processor.
 * Fixed bug in `MatchStep` where the correct was not properly determined.
-* Allow predicates to be used as options in BranchSteps.
+* Allow predicates and traversals to be used as options in `BranchStep`.
 
 [[release-3-3-7]]
 === TinkerPop 3.3.7 (Release Date: May 28, 2019)
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/PredicateTraversal.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/PredicateTraversal.java
new file mode 100644
index 0000000..aa6dab0
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/PredicateTraversal.java
@@ -0,0 +1,68 @@
+/*
+ * 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.lambda;
+
+import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException;
+
+import java.util.function.Predicate;
+
+/**
+ * @author Marko A. Rodriguez (http://markorodriguez.com)
+ */
+public final class PredicateTraversal<S> extends AbstractLambdaTraversal<S, S> {
+
+    private final Predicate predicate;
+    private S s;
+    private boolean pass;
+
+    public PredicateTraversal(final Object value) {
+        this.predicate = value instanceof Predicate ? (Predicate) value : P.eq(value);
+    }
+
+    @Override
+    public S next() {
+        if (this.pass)
+            return this.s;
+        throw FastNoSuchElementException.instance();
+    }
+
+    @Override
+    public boolean hasNext() {
+        return this.pass;
+    }
+
+    @Override
+    public void addStart(final Traverser.Admin<S> start) {
+        //noinspection unchecked
+        this.pass = this.predicate.test(this.s = start.get());
+    }
+
+    @Override
+    public String toString() {
+        return "(" + this.predicate.toString() + ")";
+    }
+
+    @Override
+    public int hashCode() {
+        return this.getClass().hashCode() ^ this.predicate.hashCode();
+    }
+}
+
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStep.java
index efc43cc..d92ce2d 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/BranchStep.java
@@ -18,8 +18,10 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step.branch;
 
+import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.lambda.PredicateTraversal;
 import org.apache.tinkerpop.gremlin.process.traversal.step.Barrier;
 import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.IdentityStep;
@@ -30,7 +32,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementExce
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
-import org.apache.tinkerpop.gremlin.util.NumberHelper;
+import org.javatuples.Pair;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -38,10 +40,10 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
@@ -50,11 +52,11 @@ import java.util.stream.Collectors;
 public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements TraversalOptionParent<M, S, E> {
 
     protected Traversal.Admin<S, M> branchTraversal;
-    protected Map<Object, List<Traversal.Admin<S, E>>> traversalOptions = new HashMap<>();
+    protected Map<Pick, List<Traversal.Admin<S, E>>> traversalPickOptions = new HashMap<>();
+    protected List<Pair<Traversal.Admin, Traversal.Admin<S, E>>> traversalOptions = new ArrayList<>();
 
     private boolean first = true;
     private boolean hasBarrier;
-    private boolean hasPredicateOptions;
 
     public BranchStep(final Traversal.Admin traversal) {
         super(traversal);
@@ -66,13 +68,20 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
 
     @Override
     public void addGlobalChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption) {
-        final Object pickTokenKey = makePickTokenKey(pickToken);
-        this.hasPredicateOptions |= pickTokenKey instanceof PredicateOption;
-        if (this.traversalOptions.containsKey(pickTokenKey))
-            this.traversalOptions.get(pickTokenKey).add(traversalOption);
-        else
-            this.traversalOptions.put(pickTokenKey, new ArrayList<>(Collections.singletonList(traversalOption)));
-
+        if (pickToken instanceof Pick) {
+            if (this.traversalPickOptions.containsKey(pickToken))
+                this.traversalPickOptions.get(pickToken).add(traversalOption);
+            else
+                this.traversalPickOptions.put((Pick) pickToken, new ArrayList<>(Collections.singletonList(traversalOption)));
+        } else {
+            final Traversal.Admin pickOptionTraversal;
+            if (pickToken instanceof Traversal) {
+                pickOptionTraversal = ((Traversal) pickToken).asAdmin();
+            } else {
+                pickOptionTraversal = new PredicateTraversal(pickToken);
+            }
+            this.traversalOptions.add(Pair.with(pickOptionTraversal, traversalOption));
+        }
         // adding an IdentityStep acts as a placeholder when reducing barriers get in the way - see the
         // standardAlgorithm() method for more information.
         if (TraversalHelper.hasStepOfAssignableClass(ReducingBarrierStep.class, traversalOption))
@@ -91,9 +100,9 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
 
     @Override
     public List<Traversal.Admin<S, E>> getGlobalChildren() {
-        return Collections.unmodifiableList(this.traversalOptions.values().stream()
-                .flatMap(List::stream)
-                .collect(Collectors.toList()));
+        return Collections.unmodifiableList(Stream.concat(
+                this.traversalPickOptions.values().stream().flatMap(List::stream),
+                this.traversalOptions.stream().map(Pair::getValue1)).collect(Collectors.toList()));
     }
 
     @Override
@@ -113,11 +122,9 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
                 // traverser as part of the condition for using that traversal option in the output. This is necessary
                 // because barriers like fold(), max(), etc. will always return true for hasNext() even if a traverser
                 // was not seeded in applyCurrentTraverser().
-                for (final List<Traversal.Admin<S, E>> options : this.traversalOptions.values()) {
-                    for (final Traversal.Admin<S, E> option : options) {
-                        if (option.getStartStep().hasNext() && option.hasNext())
-                            return option.getEndStep();
-                    }
+                for (final Traversal.Admin<S, E> option : getGlobalChildren()) {
+                    if (option.getStartStep().hasNext() && option.hasNext())
+                        return option.getEndStep();
                 }
             }
 
@@ -144,7 +151,7 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
     private void applyCurrentTraverser(final Traverser.Admin<S> start) {
         // first get the value of the choice based on the current traverser and use that to select the right traversal
         // option to which that traverser should be routed
-        final Object choice = makePickTokenKey(TraversalUtil.apply(start, this.branchTraversal));
+        final Object choice = TraversalUtil.apply(start, this.branchTraversal);
         final List<Traversal.Admin<S, E>> branches = pickBranches(choice);
 
         // if a branch is identified, then split the traverser and add it to the start of the option so that when
@@ -153,7 +160,7 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
             branches.forEach(traversal -> traversal.addStart(start.split()));
 
         if (choice != Pick.any) {
-            final List<Traversal.Admin<S, E>> anyBranch = this.traversalOptions.get(Pick.any);
+            final List<Traversal.Admin<S, E>> anyBranch = this.traversalPickOptions.get(Pick.any);
             if (null != anyBranch)
                 anyBranch.forEach(traversal -> traversal.addStart(start.split()));
         }
@@ -163,7 +170,7 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
     protected Iterator<Traverser.Admin<E>> computerAlgorithm() {
         final List<Traverser.Admin<E>> ends = new ArrayList<>();
         final Traverser.Admin<S> start = this.starts.next();
-        final Object choice = makePickTokenKey(TraversalUtil.apply(start, this.branchTraversal));
+        final Object choice = TraversalUtil.apply(start, this.branchTraversal);
         final List<Traversal.Admin<S, E>> branches = pickBranches(choice);
         if (null != branches) {
             branches.forEach(traversal -> {
@@ -174,7 +181,7 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
             });
         }
         if (choice != Pick.any) {
-            final List<Traversal.Admin<S, E>> anyBranch = this.traversalOptions.get(Pick.any);
+            final List<Traversal.Admin<S, E>> anyBranch = this.traversalPickOptions.get(Pick.any);
             if (null != anyBranch) {
                 anyBranch.forEach(traversal -> {
                     final Traverser.Admin<E> split = (Traverser.Admin<E>) start.split();
@@ -189,34 +196,37 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
 
     private List<Traversal.Admin<S, E>> pickBranches(final Object choice) {
         final List<Traversal.Admin<S, E>> branches = new ArrayList<>();
-        if (this.hasPredicateOptions) {
-            for (final Map.Entry<Object, List<Traversal.Admin<S, E>>> e : this.traversalOptions.entrySet()) {
-                if (Objects.equals(e.getKey(), choice)) {
-                    branches.addAll(e.getValue());
-                }
+        if (choice instanceof Pick) {
+            if (this.traversalPickOptions.containsKey(choice)) {
+                branches.addAll(this.traversalPickOptions.get(choice));
             }
-        } else {
-            if (this.traversalOptions.containsKey(choice)) {
-                branches.addAll(this.traversalOptions.get(choice));
+        }
+        for (final Pair<Traversal.Admin, Traversal.Admin<S, E>> p : this.traversalOptions) {
+            if (TraversalUtil.test(choice, p.getValue0())) {
+                branches.add(p.getValue1());
             }
         }
-        return branches.isEmpty() ? this.traversalOptions.get(Pick.none) : branches;
+        return branches.isEmpty() ? this.traversalPickOptions.get(Pick.none) : branches;
     }
 
     @Override
     public BranchStep<S, E, M> clone() {
         final BranchStep<S, E, M> clone = (BranchStep<S, E, M>) super.clone();
-        clone.traversalOptions = new HashMap<>(this.traversalOptions.size());
-        for (final Map.Entry<Object, List<Traversal.Admin<S, E>>> entry : this.traversalOptions.entrySet()) {
+        clone.traversalPickOptions = new HashMap<>(this.traversalPickOptions.size());
+        clone.traversalOptions = new ArrayList<>(this.traversalOptions.size());
+        for (final Map.Entry<Pick, List<Traversal.Admin<S, E>>> entry : this.traversalPickOptions.entrySet()) {
             final List<Traversal.Admin<S, E>> traversals = entry.getValue();
             if (traversals.size() > 0) {
-                final List<Traversal.Admin<S, E>> clonedTraversals = clone.traversalOptions.compute(entry.getKey(), (k, v) ->
+                final List<Traversal.Admin<S, E>> clonedTraversals = clone.traversalPickOptions.compute(entry.getKey(), (k, v) ->
                         (v == null) ? new ArrayList<>(traversals.size()) : v);
                 for (final Traversal.Admin<S, E> traversal : traversals) {
                     clonedTraversals.add(traversal.clone());
                 }
             }
         }
+        for (final Pair<Traversal.Admin, Traversal.Admin<S, E>> pair : this.traversalOptions) {
+            clone.traversalOptions.add(Pair.with(pair.getValue0().clone(), pair.getValue1().clone()));
+        }
         clone.branchTraversal = this.branchTraversal.clone();
         return clone;
     }
@@ -225,12 +235,14 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
     public void setTraversal(final Traversal.Admin<?, ?> parentTraversal) {
         super.setTraversal(parentTraversal);
         this.integrateChild(this.branchTraversal);
-        this.traversalOptions.values().stream().flatMap(List::stream).forEach(this::integrateChild);
+        this.getGlobalChildren().forEach(this::integrateChild);
     }
 
     @Override
     public int hashCode() {
         int result = super.hashCode();
+        if (this.traversalPickOptions != null)
+            result ^= this.traversalPickOptions.hashCode();
         if (this.traversalOptions != null)
             result ^= this.traversalOptions.hashCode();
         if (this.branchTraversal != null)
@@ -240,7 +252,10 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
 
     @Override
     public String toString() {
-        return StringFactory.stepString(this, this.branchTraversal, this.traversalOptions);
+        final List<Pair> combinedOptions = Stream.concat(
+                this.traversalPickOptions.entrySet().stream().map(e -> Pair.with(e.getKey(), e.getValue())),
+                this.traversalOptions.stream()).collect(Collectors.toList());
+        return StringFactory.stepString(this, this.branchTraversal, combinedOptions);
     }
 
     @Override
@@ -249,78 +264,4 @@ public class BranchStep<S, E, M> extends ComputerAwareStep<S, E> implements Trav
         this.getGlobalChildren().forEach(Traversal.Admin::reset);
         this.first = true;
     }
-
-    /**
-     * Converts numbers into {@link NumberOption} and predicates into {@link PredicateOption}.
-     */
-    private static Object makePickTokenKey(final Object o) {
-        return
-                o instanceof Number ? new NumberOption((Number) o) :
-                o instanceof Predicate ? new PredicateOption((Predicate) o) : o;
-    }
-
-    /**
-     * Wraps a single number and overrides equals/hashCode/compareTo in order to ignore the numeric data type.
-     */
-    private static class NumberOption implements Comparable<Number> {
-
-        final Number number;
-
-        NumberOption(final Number number) {
-            this.number = number;
-        }
-
-        @Override
-        public boolean equals(final Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            final NumberOption other = (NumberOption) o;
-            return 0 == NumberHelper.compare(number, other.number);
-        }
-
-        @Override
-        public int hashCode() {
-            return number.hashCode();
-        }
-
-        @Override
-        public String toString() {
-            return number.toString();
-        }
-
-        @Override
-        public int compareTo(final Number other) {
-            return NumberHelper.compare(this.number, other);
-        }
-    }
-
-    /**
-     * Wraps a single predicate and overrides equals/hashCode so that the predicate can be matched against a concrete value.
-     */
-    private static class PredicateOption {
-
-        final Predicate predicate;
-
-        PredicateOption(final Predicate predicate) {
-            this.predicate = predicate;
-        }
-
-        @SuppressWarnings({"EqualsWhichDoesntCheckParameterClass", "unchecked"})
-        @Override
-        public boolean equals(final Object o) {
-            if (o instanceof PredicateOption)
-                return ((PredicateOption) o).predicate.equals(predicate);
-            return predicate.test(o);
-        }
-
-        @Override
-        public int hashCode() {
-            return 0;
-        }
-
-        @Override
-        public String toString() {
-            return predicate.toString();
-        }
-    }
 }
\ No newline at end of file
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java
index e699f22..a804d8f 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/ChooseStep.java
@@ -42,10 +42,12 @@ public final class ChooseStep<S, E, M> extends BranchStep<S, E, M> {
 
     @Override
     public void addGlobalChildOption(final M pickToken, final Traversal.Admin<S, E> traversalOption) {
-        if (Pick.any.equals(pickToken))
-            throw new IllegalArgumentException("Choose step can not have an any-option as only one option per traverser is allowed");
-        if (this.traversalOptions.containsKey(pickToken))
-            throw new IllegalArgumentException("Choose step can only have one traversal per pick token: " + pickToken);
+        if (pickToken instanceof Pick) {
+            if (Pick.any.equals(pickToken))
+                throw new IllegalArgumentException("Choose step can not have an any-option as only one option per traverser is allowed");
+            if (this.traversalPickOptions.containsKey(pickToken))
+                throw new IllegalArgumentException("Choose step can only have one traversal per pick token: " + pickToken);
+        }
         super.addGlobalChildOption(pickToken, traversalOption);
     }
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/UnionStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/UnionStep.java
index 85f3645..de70479 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/UnionStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/UnionStep.java
@@ -47,6 +47,6 @@ public final class UnionStep<S, E> extends BranchStep<S, E, TraversalOptionParen
 
     @Override
     public String toString() {
-        return StringFactory.stepString(this, this.traversalOptions.getOrDefault(Pick.any, Collections.emptyList()));
+        return StringFactory.stepString(this, this.traversalPickOptions.getOrDefault(Pick.any, Collections.emptyList()));
     }
 }
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/ComplexTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/ComplexTest.java
index a3bb1cd..592e945 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/ComplexTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/ComplexTest.java
@@ -252,9 +252,10 @@ public abstract class ComplexTest extends AbstractGremlinProcessTest {
         assertTrue(map.get("vadas").contains("pretty young"));
         assertTrue(map.get("vadas").contains("younger than peter"));
         assertTrue(map.containsKey("josh"));
-        assertEquals(2, map.get("josh").size());
+        assertEquals(3, map.get("josh").size());
         assertTrue(map.get("josh").contains("younger than peter"));
         assertTrue(map.get("josh").contains("pretty old"));
+        assertTrue(map.get("josh").contains("looks like josh"));
         assertTrue(map.containsKey("peter"));
         assertEquals(1, map.get("peter").size());
         assertTrue(map.get("peter").contains("pretty old"));
@@ -348,6 +349,7 @@ public abstract class ComplexTest extends AbstractGremlinProcessTest {
                         .by("name")
                         .by(branch(__.values("age"))
                                 .option(29, constant("almost old"))
+                                .option(__.is(32), constant("looks like josh"))
                                 .option(lt(29), constant("pretty young"))
                                 .option(lt(35), constant("younger than peter"))
                                 .option(gte(30), constant("pretty old")).fold());
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphPlayTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphPlayTest.java
index 3b241bc..3216803 100644
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphPlayTest.java
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphPlayTest.java
@@ -130,16 +130,18 @@ public class TinkerGraphPlayTest {
     public void testPlayDK() throws Exception {
 
         GraphTraversalSource g = TinkerFactory.createModern().traversal();
-        g./*withComputer().*/V().hasLabel("person")
+        System.out.println(g./*withComputer().*/V().hasLabel("person")
                 .project("name", "age", "comments")
                     .by("name")
                     .by("age")
                     .by(branch(values("age"))
+                            .option(TraversalOptionParent.Pick.any, constant("foo"))
                             .option(29, constant("almost old"))
+                            .option(__.is(32), constant("looks like josh"))
                             .option(lt(29), constant("pretty young"))
                             .option(lt(35), constant("younger than peter"))
-                            .option(gte(30), constant("pretty old")).fold())
-                .forEachRemaining(System.out::println);
+                            .option(gte(30), constant("pretty old")).fold()).explain());
+                //.forEachRemaining(System.out::println);
     }
 
     @Test