TINKERPOP-2041 Implemented text predicates

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

Branch: refs/heads/TINKERPOP-2041
Commit: 9d83f8af6562a54d6e2a200e35de145b9ca26062
Parents: cce3fe6
Author: Daniel Kuppitz <daniel_kupp...@hotmail.com>
Authored: Wed Sep 26 15:44:35 2018 -0700
Committer: Daniel Kuppitz <daniel_kupp...@hotmail.com>
Committed: Fri Sep 28 12:56:27 2018 -0700

----------------------------------------------------------------------
 CHANGELOG.asciidoc                              |   1 +
 docs/src/reference/the-traversal.asciidoc       |  34 +++--
 docs/src/upgrade/release-3.4.x.asciidoc         |  15 +++
 .../tinkerpop/gremlin/jsr223/CoreImports.java   |   3 +
 .../tinkerpop/gremlin/process/traversal/TP.java | 107 ++++++++++++++++
 .../gremlin/process/traversal/Text.java         | 123 +++++++++++++++++++
 .../structure/io/graphson/GraphSONModule.java   |   5 +
 .../io/graphson/TraversalSerializersV2d0.java   |  35 ++++++
 .../io/graphson/TraversalSerializersV3d0.java   |  35 ++++++
 .../structure/io/gryo/GryoSerializersV1d0.java  |  21 ++++
 .../structure/io/gryo/GryoSerializersV3d0.java  |  21 ++++
 .../gremlin/structure/io/gryo/GryoVersion.java  |   7 +-
 .../gremlin/process/traversal/PTest.java        |  15 +++
 .../GraphSONMapperPartialEmbeddedTypeTest.java  |  14 ++-
 gremlin-dotnet/glv/TP.template                  |  71 +++++++++++
 gremlin-dotnet/glv/generate.groovy              |  14 ++-
 .../src/Gremlin.Net/Process/Traversal/TP.cs     |  96 +++++++++++++++
 .../Structure/IO/GraphSON/GraphSONWriter.cs     |   3 +-
 .../Structure/IO/GraphSON/TPSerializer.cs       |  45 +++++++
 .../gremlin/groovy/jsr223/GroovyTranslator.java |   7 ++
 gremlin-javascript/glv/TraversalSource.template |  45 ++++++-
 gremlin-javascript/glv/generate.groovy          |   7 ++
 .../main/javascript/gremlin-javascript/index.js |   1 +
 .../gremlin-javascript/lib/process/traversal.js |  63 +++++++++-
 .../lib/structure/io/type-serializers.js        |  24 +++-
 .../test/cucumber/feature-steps.js              |   1 +
 gremlin-python/glv/TraversalSource.template     |  21 +++-
 gremlin-python/glv/generate.groovy              |   9 +-
 .../gremlin/python/jsr223/PythonTranslator.java |   7 ++
 .../jython/gremlin_python/process/traversal.py  |  63 +++++++++-
 .../gremlin_python/structure/io/graphsonV2d0.py |  13 +-
 .../gremlin_python/structure/io/graphsonV3d0.py |  13 +-
 .../src/main/jython/radish/feature_steps.py     |   3 +-
 gremlin-test/features/filter/Has.feature        |  57 ++++++++-
 .../process/traversal/step/filter/HasTest.java  |  86 +++++++++++++
 35 files changed, 1056 insertions(+), 29 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/CHANGELOG.asciidoc
----------------------------------------------------------------------
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 5e63da0..e6f3cd8 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -25,6 +25,7 @@ 
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 
 This release also includes changes from <<release-3-3-3, 3.3.3>>.
 
+* Added text predicates.
 * Rewrote `ConnectiveStrategy` to support an arbitrary number of infix 
notations in a single traversal.
 * GraphSON `MessageSerializer`s will automatically register the 
GremlinServerModule to a provided GraphSONMapper.
 * Removed support for `-i` option in Gremlin Server which was previously 
deprecated.

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/docs/src/reference/the-traversal.asciidoc
----------------------------------------------------------------------
diff --git a/docs/src/reference/the-traversal.asciidoc 
b/docs/src/reference/the-traversal.asciidoc
index 6146f9b..da7260c 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -3356,24 +3356,32 @@ interface. Steps that allow for this type of modulation 
will explicitly state so
 [[a-note-on-predicates]]
 == A Note on Predicates
 
-A `P` is a predicate of the form `Function<Object,Boolean>`. That is, given 
some object, return true or false. The
-provided predicates are outlined in the table below and are used in various 
steps such as <<has-step,`has()`>>-step,
+A `P` is a predicate of the form `Function<Object,Boolean>`. That is, given 
some object, return true or false. As of
+the relase of TinkerPop 3.4.0, Gremlin also supports simple text predicates, 
which only work on `String` values. The `TP`
+text predicates extends the `P` predicates, but are specialized in that they 
are of the form `Function<String,Boolean>`.
+The provided predicates are outlined in the table below and are used in 
various steps such as <<has-step,`has()`>>-step,
 <<where-step,`where()`>>-step, <<is-step,`is()`>>-step, etc.
 
 [width="100%",cols="3,15",options="header"]
 |=========================================================
 | Predicate | Description
-| `eq(object)` | Is the incoming object equal to the provided object?
-| `neq(object)` | Is the incoming object not equal to the provided object?
-| `lt(number)` | Is the incoming number less than the provided number?
-| `lte(number)` | Is the incoming number less than or equal to the provided 
number?
-| `gt(number)` | Is the incoming number greater than the provided number?
-| `gte(number)` | Is the incoming number greater than or equal to the provided 
number?
-| `inside(number,number)` | Is the incoming number greater than the first 
provided number and less than the second?
-| `outside(number,number)` | Is the incoming number less than the first 
provided number or greater than the second?
-| `between(number,number)` | Is the incoming number greater than or equal to 
the first provided number and less than the second?
-| `within(objects...)` | Is the incoming object in the array of provided 
objects?
-| `without(objects...)` | Is the incoming object not in the array of the 
provided objects?
+| `P.eq(object)` | Is the incoming object equal to the provided object?
+| `P.neq(object)` | Is the incoming object not equal to the provided object?
+| `P.lt(number)` | Is the incoming number less than the provided number?
+| `P.lte(number)` | Is the incoming number less than or equal to the provided 
number?
+| `P.gt(number)` | Is the incoming number greater than the provided number?
+| `P.gte(number)` | Is the incoming number greater than or equal to the 
provided number?
+| `P.inside(number,number)` | Is the incoming number greater than the first 
provided number and less than the second?
+| `P.outside(number,number)` | Is the incoming number less than the first 
provided number or greater than the second?
+| `P.between(number,number)` | Is the incoming number greater than or equal to 
the first provided number and less than the second?
+| `P.within(objects...)` | Is the incoming object in the array of provided 
objects?
+| `P.without(objects...)` | Is the incoming object not in the array of the 
provided objects?
+| `TP.startsWith(string)` | Does the incoming `String` start with the provided 
`String`?
+| `TP.endsWith(string)` | Does the incoming `String` end with the provided 
`String`?
+| `TP.contains(string)` | Does the incoming `String` contain the provided 
`String`?
+| `TP.startsNotWith(string)` | TODO: find a better name
+| `TP.endsNotWith(string)` | TODO: find a better name
+| `TP.absent(string)` | TODO: find a better name
 |=========================================================
 
 [gremlin-groovy]

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/docs/src/upgrade/release-3.4.x.asciidoc
----------------------------------------------------------------------
diff --git a/docs/src/upgrade/release-3.4.x.asciidoc 
b/docs/src/upgrade/release-3.4.x.asciidoc
index e0484a8..d677a5c 100644
--- a/docs/src/upgrade/release-3.4.x.asciidoc
+++ b/docs/src/upgrade/release-3.4.x.asciidoc
@@ -29,6 +29,21 @@ Please see the 
link:https://github.com/apache/tinkerpop/blob/3.4.0/CHANGELOG.asc
 
 === Upgrading for Users
 
+==== Added text predicates
+
+Gremlin now supports simple text predicates on top of the existing `P` 
predicates. Both, the new `TP` text predicates and the old `P` predicates, can 
be chained using `and()` and `or()`.
+
+[source,groovy]
+----
+gremlin> g.V().has("person","name", contains("o")).valueMap()
+==>[name:[marko],age:[29]]
+==>[name:[josh],age:[32]]
+gremlin> g.V().has("person","name", 
contains("o").and(gte("j").and(endsWith("ko")))).valueMap()
+==>[name:[marko],age:[29]]
+----
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2041[TINKERPOP-2041]
+
 ==== Changed infix behavior
 
 The infix notation of `and()` and `or()` now supports an arbitrary number of 
traversals and `ConnectiveStrategy` produces a traversal with proper AND and OR 
semantics.

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
index 2b1e33e..38ef258 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
@@ -67,6 +67,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
 import org.apache.tinkerpop.gremlin.process.traversal.Scope;
+import org.apache.tinkerpop.gremlin.process.traversal.TP;
 import org.apache.tinkerpop.gremlin.process.traversal.Translator;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
 import 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
@@ -181,6 +182,7 @@ public final class CoreImports {
         CLASS_IMPORTS.add(TraversalOptionParent.class);
         CLASS_IMPORTS.add(TraversalOptionParent.Pick.class);
         CLASS_IMPORTS.add(P.class);
+        CLASS_IMPORTS.add(TP.class);
         // remote
         CLASS_IMPORTS.add(RemoteConnection.class);
         CLASS_IMPORTS.add(RemoteGraph.class);
@@ -291,6 +293,7 @@ public final class CoreImports {
 
         uniqueMethods(IoCore.class).forEach(METHOD_IMPORTS::add);
         uniqueMethods(P.class).forEach(METHOD_IMPORTS::add);
+        uniqueMethods(TP.class).forEach(METHOD_IMPORTS::add);
         uniqueMethods(__.class).filter(m -> 
!m.getName().equals("__")).forEach(METHOD_IMPORTS::add);
         uniqueMethods(Computer.class).forEach(METHOD_IMPORTS::add);
         uniqueMethods(TimeUtil.class).forEach(METHOD_IMPORTS::add);

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TP.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TP.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TP.java
new file mode 100644
index 0000000..fc245a4
--- /dev/null
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TP.java
@@ -0,0 +1,107 @@
+/*
+ * 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;
+
+import java.util.function.BiPredicate;
+
+/**
+ * @author Daniel Kuppitz (http://gremlin.guru)
+ */
+public class TP extends P<String> {
+
+    @SuppressWarnings("WeakerAccess")
+    public TP(final BiPredicate<String, String> biPredicate, final String 
value) {
+        super(biPredicate, value);
+    }
+
+    @Override
+    public boolean equals(final Object other) {
+        return other instanceof TP && super.equals(other);
+    }
+
+    @Override
+    public String toString() {
+        return null == this.originalValue ? this.biPredicate.toString() : 
this.biPredicate.toString() + "(" + this.originalValue + ")";
+    }
+
+    @Override
+    public TP negate() {
+        return new TP(this.biPredicate.negate(), this.originalValue);
+    }
+
+    public TP clone() {
+        return (TP) super.clone();
+    }
+
+    //////////////// statics
+
+    /**
+     * Determines if String does start with the given value.
+     *
+     * @since 3.4.0
+     */
+    public static TP startsWith(final String value) {
+        return new TP(Text.startsWith, value);
+    }
+
+    /**
+     * Determines if String does not start with the given value.
+     *
+     * @since 3.4.0
+     */
+    public static TP startsNotWith(final String value) {
+        return new TP(Text.startsNotWith, value);
+    }
+
+    /**
+     * Determines if String does start with the given value.
+     *
+     * @since 3.4.0
+     */
+    public static TP endsWith(final String value) {
+        return new TP(Text.endsWith, value);
+    }
+
+    /**
+     * Determines if String does not start with the given value.
+     *
+     * @since 3.4.0
+     */
+    public static TP endsNotWith(final String value) {
+        return new TP(Text.endsNotWith, value);
+    }
+
+    /**
+     * Determines if String does contain the given value.
+     *
+     * @since 3.4.0
+     */
+    public static TP contains(final String value) {
+        return new TP(Text.contains, value);
+    }
+
+    /**
+     * Determines if String does not contain the given value.
+     *
+     * @since 3.4.0
+     */
+    public static TP absent(final String value) {
+        return new TP(Text.absent, value);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java
new file mode 100644
index 0000000..5169309
--- /dev/null
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java
@@ -0,0 +1,123 @@
+/*
+ * 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;
+
+import java.util.function.BiPredicate;
+
+/**
+ * @author Daniel Kuppitz (http://gremlin.guru)
+ */
+public enum Text implements BiPredicate<String, String> {
+
+    startsWith {
+        @Override
+        public boolean test(final String value, final String prefix) {
+            return value.startsWith(prefix);
+        }
+
+        /**
+         * The negative of {@code startsWith} is {@link #startsNotWith}.
+         */
+        @Override
+        public Text negate() {
+            return startsNotWith;
+        }
+    },
+
+    startsNotWith {
+        @Override
+        public boolean test(final String value, final String prefix) {
+            return !startsWith.test(value, prefix);
+        }
+
+        /**
+         * The negative of {@code startsNotWith} is {@link #startsWith}.
+         */
+        @Override
+        public Text negate() {
+            return startsWith;
+        }
+    },
+
+    endsWith {
+        @Override
+        public boolean test(final String value, final String suffix) {
+            return value.endsWith(suffix);
+        }
+
+        /**
+         * The negative of {@code endsWith} is {@link #endsNotWith}.
+         */
+        @Override
+        public Text negate() {
+            return endsNotWith;
+        }
+    },
+
+    endsNotWith {
+        @Override
+        public boolean test(final String value, final String prefix) {
+            return !endsWith.test(value, prefix);
+        }
+
+        /**
+         * The negative of {@code endsNotWith} is {@link #endsWith}.
+         */
+        @Override
+        public Text negate() {
+            return endsWith;
+        }
+    },
+
+    contains {
+        @Override
+        public boolean test(final String value, final String search) {
+            return value.contains(search);
+        }
+
+        /**
+         * The negative of {@code contains} is {@link #absent}.
+         */
+        @Override
+        public Text negate() {
+            return absent;
+        }
+    },
+
+    absent{
+        @Override
+        public boolean test(final String value, final String search) {
+            return !contains.test(value, search);
+        }
+
+        /**
+         * The negative of {@code absent} is {@link #contains}.
+         */
+        @Override
+        public Text negate() {
+            return contains;
+        }
+    };
+
+    /**
+     * Produce the opposite representation of the current {@code Text} enum.
+     */
+    @Override
+    public abstract Text negate();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
----------------------------------------------------------------------
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 1bccd7c..74647a1 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
@@ -28,6 +28,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Path;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
 import org.apache.tinkerpop.gremlin.process.traversal.Scope;
+import org.apache.tinkerpop.gremlin.process.traversal.TP;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
@@ -145,6 +146,7 @@ abstract class GraphSONModule extends 
TinkerPopJacksonModule {
                     put(AndP.class, "P");
                     put(OrP.class, "P");
                     put(P.class, "P");
+                    put(TP.class, "TP");
                     Stream.of(
                             VertexProperty.Cardinality.class,
                             Column.class,
@@ -270,6 +272,7 @@ abstract class GraphSONModule extends 
TinkerPopJacksonModule {
                     TraversalOptionParent.Pick.values(),
                     T.values()).flatMap(Stream::of).forEach(e -> 
addDeserializer(e.getClass(), new 
TraversalSerializersV3d0.EnumJacksonDeserializer(e.getDeclaringClass())));
             addDeserializer(P.class, new 
TraversalSerializersV3d0.PJacksonDeserializer());
+            addDeserializer(TP.class, new 
TraversalSerializersV3d0.TPJacksonDeserializer());
             addDeserializer(Lambda.class, new 
TraversalSerializersV3d0.LambdaJacksonDeserializer());
             addDeserializer(Traverser.class, new 
TraversalSerializersV3d0.TraverserJacksonDeserializer());
             Arrays.asList(
@@ -359,6 +362,7 @@ abstract class GraphSONModule extends 
TinkerPopJacksonModule {
                     put(AndP.class, "P");
                     put(OrP.class, "P");
                     put(P.class, "P");
+                    put(TP.class, "TP");
                     Stream.of(
                             VertexProperty.Cardinality.class,
                             Column.class,
@@ -476,6 +480,7 @@ abstract class GraphSONModule extends 
TinkerPopJacksonModule {
                     TraversalOptionParent.Pick.values(),
                     T.values()).flatMap(Stream::of).forEach(e -> 
addDeserializer(e.getClass(), new 
TraversalSerializersV2d0.EnumJacksonDeserializer(e.getDeclaringClass())));
             addDeserializer(P.class, new 
TraversalSerializersV2d0.PJacksonDeserializer());
+            addDeserializer(TP.class, new 
TraversalSerializersV2d0.TPJacksonDeserializer());
             addDeserializer(Lambda.class, new 
TraversalSerializersV2d0.LambdaJacksonDeserializer());
             addDeserializer(Traverser.class, new 
TraversalSerializersV2d0.TraverserJacksonDeserializer());
             Arrays.asList(

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2d0.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2d0.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2d0.java
index 2a07723..7ba4ca5 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2d0.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2d0.java
@@ -24,6 +24,7 @@ import org.apache.commons.configuration.MapConfiguration;
 import 
org.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.TP;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
@@ -372,6 +373,40 @@ final class TraversalSerializersV2d0 {
         }
     }
 
+    final static class TPJacksonDeserializer extends StdDeserializer<TP> {
+
+        public TPJacksonDeserializer() {
+            super(TP.class);
+        }
+
+        @Override
+        public TP deserialize(final JsonParser jsonParser, final 
DeserializationContext deserializationContext) throws IOException, 
JsonProcessingException {
+            String predicate = null;
+            String value = null;
+
+            while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
+                if 
(jsonParser.getCurrentName().equals(GraphSONTokens.PREDICATE)) {
+                    jsonParser.nextToken();
+                    predicate = jsonParser.getText();
+                } else if 
(jsonParser.getCurrentName().equals(GraphSONTokens.VALUE)) {
+                    jsonParser.nextToken();
+                    value = deserializationContext.readValue(jsonParser, 
String.class);
+                }
+            }
+
+            try {
+                return (TP) TP.class.getMethod(predicate, 
String.class).invoke(null, value);
+            } catch (final Exception e) {
+                throw new IllegalStateException(e.getMessage(), e);
+            }
+        }
+
+        @Override
+        public boolean isCachable() {
+            return true;
+        }
+    }
+
     final static class LambdaJacksonDeserializer extends 
StdDeserializer<Lambda> {
 
         public LambdaJacksonDeserializer() {

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3d0.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3d0.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3d0.java
index eaa7b0f..ca01ec0 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3d0.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3d0.java
@@ -24,6 +24,7 @@ import org.apache.commons.configuration.MapConfiguration;
 import 
org.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.TP;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
@@ -370,6 +371,40 @@ final class TraversalSerializersV3d0 {
         }
     }
 
+    final static class TPJacksonDeserializer extends StdDeserializer<TP> {
+
+        public TPJacksonDeserializer() {
+            super(TP.class);
+        }
+
+        @Override
+        public TP deserialize(final JsonParser jsonParser, final 
DeserializationContext deserializationContext) throws IOException, 
JsonProcessingException {
+            String predicate = null;
+            String value = null;
+
+            while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
+                if 
(jsonParser.getCurrentName().equals(GraphSONTokens.PREDICATE)) {
+                    jsonParser.nextToken();
+                    predicate = jsonParser.getText();
+                } else if 
(jsonParser.getCurrentName().equals(GraphSONTokens.VALUE)) {
+                    jsonParser.nextToken();
+                    value = deserializationContext.readValue(jsonParser, 
String.class);
+                }
+            }
+
+            try {
+                return (TP) TP.class.getMethod(predicate, 
String.class).invoke(null, value);
+            } catch (final Exception e) {
+                throw new IllegalStateException(e.getMessage(), e);
+            }
+        }
+
+        @Override
+        public boolean isCachable() {
+            return true;
+        }
+    }
+
     final static class LambdaJacksonDeserializer extends 
StdDeserializer<Lambda> {
 
         public LambdaJacksonDeserializer() {

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV1d0.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV1d0.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV1d0.java
index ca7c241..c7de4ec 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV1d0.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV1d0.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.Path;
+import org.apache.tinkerpop.gremlin.process.traversal.TP;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.util.AndP;
 import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP;
@@ -228,6 +229,26 @@ public final class GryoSerializersV1d0 {
         }
     }
 
+    public final static class TPSerializer implements SerializerShim<TP> {
+        @Override
+        public <O extends OutputShim> void write(final KryoShim<?, O> kryo, 
final O output, final TP p) {
+            output.writeString(p.getBiPredicate().toString());
+            kryo.writeObject(output, p.getValue());
+        }
+
+        @Override
+        public <I extends InputShim> TP read(final KryoShim<I, ?> kryo, final 
I input, final Class<TP> clazz) {
+            final String predicate = input.readString();
+            final String value = kryo.readObject(input, String.class);
+
+            try {
+                return (TP) TP.class.getMethod(predicate, 
String.class).invoke(null, value);
+            } catch (final Exception e) {
+                throw new IllegalStateException(e.getMessage(), e);
+            }
+        }
+    }
+
     public final static class LambdaSerializer implements 
SerializerShim<Lambda> {
         @Override
         public <O extends OutputShim> void write(final KryoShim<?, O> kryo, 
final O output, final Lambda lambda) {

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV3d0.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV3d0.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV3d0.java
index fee9345..ffda00e 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV3d0.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV3d0.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.Path;
+import org.apache.tinkerpop.gremlin.process.traversal.TP;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.util.AndP;
 import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP;
@@ -344,6 +345,26 @@ public final class GryoSerializersV3d0 {
         }
     }
 
+    public final static class TPSerializer implements SerializerShim<TP> {
+        @Override
+        public <O extends OutputShim> void write(final KryoShim<?, O> kryo, 
final O output, final TP p) {
+            output.writeString(p.getBiPredicate().toString());
+            kryo.writeObject(output, p.getValue());
+        }
+
+        @Override
+        public <I extends InputShim> TP read(final KryoShim<I, ?> kryo, final 
I input, final Class<TP> clazz) {
+            final String predicate = input.readString();
+            final String value = kryo.readObject(input, String.class);
+
+            try {
+                return (TP) TP.class.getMethod(predicate, 
String.class).invoke(null, value);
+            } catch (final Exception e) {
+                throw new IllegalStateException(e.getMessage(), e);
+            }
+        }
+    }
+
     public final static class LambdaSerializer implements 
SerializerShim<Lambda> {
         @Override
         public <O extends OutputShim> void write(final KryoShim<?, O> kryo, 
final O output, final Lambda lambda) {

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
index 7af3766..fb62fee 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
@@ -33,6 +33,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Path;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
 import org.apache.tinkerpop.gremlin.process.traversal.Scope;
+import org.apache.tinkerpop.gremlin.process.traversal.TP;
 import 
org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
 import 
org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeGlobalStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.FoldStep;
@@ -244,7 +245,7 @@ public enum GryoVersion {
             add(GryoTypeReg.of(Collections.singleton(null).getClass(), 54));
             add(GryoTypeReg.of(Collections.singletonList(null).getClass(), 
24));
             add(GryoTypeReg.of(Collections.singletonMap(null, 
null).getClass(), 23));
-            add(GryoTypeReg.of(Types.COLLECTIONS_SYNCHRONIZED_MAP, 185, new 
UtilSerializers.SynchronizedMapSerializer()));  // ***LAST ID***
+            add(GryoTypeReg.of(Types.COLLECTIONS_SYNCHRONIZED_MAP, 185, new 
UtilSerializers.SynchronizedMapSerializer()));
             add(GryoTypeReg.of(Contains.class, 49));
             add(GryoTypeReg.of(Currency.class, 40));
             add(GryoTypeReg.of(Date.class, 38));
@@ -311,6 +312,7 @@ public enum GryoVersion {
 
             add(GryoTypeReg.of(Bytecode.class, 122, new 
GryoSerializersV3d0.BytecodeSerializer()));
             add(GryoTypeReg.of(P.class, 124, new 
GryoSerializersV3d0.PSerializer()));
+            add(GryoTypeReg.of(TP.class, 186, new 
GryoSerializersV3d0.TPSerializer())); // ***LAST ID***
             add(GryoTypeReg.of(Lambda.class, 125, new 
GryoSerializersV3d0.LambdaSerializer()));
             add(GryoTypeReg.of(Bytecode.Binding.class, 126, new 
GryoSerializersV3d0.BindingSerializer()));
             add(GryoTypeReg.of(Order.class, 127));
@@ -436,7 +438,7 @@ public enum GryoVersion {
             add(GryoTypeReg.of(Collections.singleton(null).getClass(), 54));
             add(GryoTypeReg.of(Collections.singletonList(null).getClass(), 
24));
             add(GryoTypeReg.of(Collections.singletonMap(null, 
null).getClass(), 23));
-            add(GryoTypeReg.of(Types.COLLECTIONS_SYNCHRONIZED_MAP, 185, new 
UtilSerializers.SynchronizedMapSerializer()));  // ***LAST ID***
+            add(GryoTypeReg.of(Types.COLLECTIONS_SYNCHRONIZED_MAP, 185, new 
UtilSerializers.SynchronizedMapSerializer()));
             add(GryoTypeReg.of(Contains.class, 49));
             add(GryoTypeReg.of(Currency.class, 40));
             add(GryoTypeReg.of(Date.class, 38));
@@ -502,6 +504,7 @@ public enum GryoVersion {
 
             add(GryoTypeReg.of(Bytecode.class, 122, new 
GryoSerializersV1d0.BytecodeSerializer()));
             add(GryoTypeReg.of(P.class, 124, new 
GryoSerializersV1d0.PSerializer()));
+            add(GryoTypeReg.of(TP.class, 186, new 
GryoSerializersV1d0.TPSerializer())); // ***LAST ID***
             add(GryoTypeReg.of(Lambda.class, 125, new 
GryoSerializersV1d0.LambdaSerializer()));
             add(GryoTypeReg.of(Bytecode.Binding.class, 126, new 
GryoSerializersV1d0.BindingSerializer()));
             add(GryoTypeReg.of(Order.class, 127));

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java
index 6ec33cc..1060b4e 100644
--- 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java
@@ -94,6 +94,21 @@ public class PTest {
                     {P.between("m", "n").or(P.eq("daniel")), "marko", true},
                     {P.between("m", "n").or(P.eq("daniel")), "daniel", true},
                     {P.between("m", "n").or(P.eq("daniel")), "stephen", false},
+                    // text predicates
+                    {TP.contains("ark"), "marko", true},
+                    {TP.contains("ark"), "josh", false},
+                    {TP.startsWith("jo"), "marko", false},
+                    {TP.startsWith("jo"), "josh", true},
+                    {TP.endsWith("ter"), "marko", false},
+                    {TP.endsWith("ter"), "peter", true},
+                    {TP.contains("o"), "marko", true},
+                    {TP.contains("o"), "josh", true},
+                    {TP.contains("o").and(P.gte("j")), "marko", true},
+                    {TP.contains("o").and(P.gte("j")), "josh", true},
+                    {TP.contains("o").and(P.gte("j")).and(TP.endsWith("ko")), 
"marko", true},
+                    {TP.contains("o").and(P.gte("j")).and(TP.endsWith("ko")), 
"josh", false},
+                    {TP.contains("o").and(P.gte("j").and(TP.endsWith("ko"))), 
"marko", true},
+                    {TP.contains("o").and(P.gte("j").and(TP.endsWith("ko"))), 
"josh", false},
             }));
         }
 

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapperPartialEmbeddedTypeTest.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapperPartialEmbeddedTypeTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapperPartialEmbeddedTypeTest.java
index 4e86ebd..52a7ee4 100644
--- 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapperPartialEmbeddedTypeTest.java
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONMapperPartialEmbeddedTypeTest.java
@@ -21,6 +21,7 @@ package org.apache.tinkerpop.gremlin.structure.io.graphson;
 import 
org.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.TP;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.shaded.jackson.databind.JsonMappingException;
 import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
@@ -327,10 +328,19 @@ public class GraphSONMapperPartialEmbeddedTypeTest 
extends AbstractGraphSONTest
                 P.without(1,2,3,4),
                 P.without(Arrays.asList(1,2,3,4)),
                 P.eq(1).and(P.eq(2)),
-                P.eq(1).or(P.eq(2)));
+                P.eq(1).or(P.eq(2)),
+                TP.contains("ark"),
+                TP.startsWith("mar"),
+                TP.endsWith("ko"),
+                TP.endsWith("ko").and(P.gte("mar")),
+                P.gte("mar").and(TP.endsWith("ko")));
 
         for (P p : variantsOfP) {
-            assertEquals(p, serializeDeserialize(mapper, p, P.class));
+            if (p instanceof TP) {
+                assertEquals(p, serializeDeserialize(mapper, p, TP.class));
+            } else {
+                assertEquals(p, serializeDeserialize(mapper, p, P.class));
+            }
         }
     }
 

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-dotnet/glv/TP.template
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/glv/TP.template b/gremlin-dotnet/glv/TP.template
new file mode 100644
index 0000000..22320ea
--- /dev/null
+++ b/gremlin-dotnet/glv/TP.template
@@ -0,0 +1,71 @@
+#region License
+
+/*
+ * 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.
+ */
+
+#endregion
+
+// THIS IS A GENERATED FILE - DO NOT MODIFY THIS FILE DIRECTLY - see pom.xml
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Gremlin.Net.Process.Traversal
+{
+#pragma warning disable 1591
+
+    /// <summary>
+    ///     A <see cref="TP" /> is a predicate of the form Func&lt;string, 
bool&gt;.
+    ///     That is, given some string, return true or false.
+    /// </summary>
+    public class TP : P
+    {
+        /// <summary>
+        ///     Initializes a new instance of the <see cref="TP" /> class.
+        /// </summary>
+        /// <param name="operatorName">The name of the predicate.</param>
+        /// <param name="value">The value of the predicate.</param>
+        /// <param name="other">An optional other predicate that is used as an 
argument for this predicate.</param>
+        public TP(string operatorName, string value, P other = null) : 
base(operatorName, value, other)
+        {
+        }
+
+<% tpmethods.each { method -> %>
+        public static TP <%= toCSharpMethodName.call(method) %>(string value)
+        {
+            return new TP("<%= method %>", value);
+        }
+<% } %>
+
+        private static T[] ToGenericArray<T>(ICollection<T> collection)
+        {
+            return collection?.ToArray() ?? new T[0];
+        }
+
+        /// <inheritdoc />
+        public override string ToString()
+        {
+            return Other == null ? \$"{OperatorName}({Value})" : 
\$"{OperatorName}({Value},{Other})";
+        }
+    }
+
+#pragma warning restore 1591
+}

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-dotnet/glv/generate.groovy
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/glv/generate.groovy 
b/gremlin-dotnet/glv/generate.groovy
index 0c93f3d..3dbd959 100644
--- a/gremlin-dotnet/glv/generate.groovy
+++ b/gremlin-dotnet/glv/generate.groovy
@@ -26,6 +26,7 @@ import 
org.apache.tinkerpop.gremlin.process.traversal.TraversalSource
 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.P
+import org.apache.tinkerpop.gremlin.process.traversal.TP
 import org.apache.tinkerpop.gremlin.process.traversal.IO
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__
 import org.apache.tinkerpop.gremlin.structure.Direction
@@ -59,6 +60,7 @@ def toCSharpTypeMap = ["Long": "long",
                        "Traversal[]": "ITraversal[]",
                        "Predicate": "IPredicate",
                        "P": "P",
+                       "TP": "TP",
                        "TraversalStrategy": "ITraversalStrategy",
                        "TraversalStrategy[]": "ITraversalStrategy[]",
                        "Function": "IFunction",
@@ -241,6 +243,12 @@ def binding = ["pmethods": P.class.getMethods().
         collect { it.name }.
         unique().
         sort { a, b -> a <=> b },
+               "tpmethods": TP.class.getMethods().
+                       findAll { Modifier.isStatic(it.getModifiers()) }.
+                       findAll { TP.class.isAssignableFrom(it.returnType) }.
+                       collect { it.name }.
+                       unique().
+                       sort { a, b -> a <=> b },
                "sourceStepMethods": GraphTraversalSource.getMethods(). // 
SOURCE STEPS
                         findAll { 
GraphTraversalSource.class.equals(it.returnType) }.
                         findAll {
@@ -334,6 +342,10 @@ def pTemplate = engine.createTemplate(new 
File("${projectBaseDir}/glv/P.template
 def pFile = new 
File("${projectBaseDir}/src/Gremlin.Net/Process/Traversal/P.cs")
 pFile.newWriter().withWriter{ it << pTemplate }
 
+def tpTemplate = engine.createTemplate(new 
File("${projectBaseDir}/glv/TP.template")).make(binding)
+def tpFile = new 
File("${projectBaseDir}/src/Gremlin.Net/Process/Traversal/TP.cs")
+tpFile.newWriter().withWriter{ it << tpTemplate }
+
 binding.tokens.each {k,v ->
     def tokenTemplate = engine.createTemplate(new 
File("${projectBaseDir}/glv/Token.template")).make([tokenFields: v, tokenName: 
k])
     def tokenFile = new 
File("${projectBaseDir}/src/Gremlin.Net/Process/Traversal/${k}.cs")
@@ -382,4 +394,4 @@ def templateCsprojFile = new 
File("${projectBaseDir}/src/Gremlin.Net.Template/Gr
 templateCsprojFile.newWriter().withWriter{ it << templateCsprojTemplate }
 def nuspecTemplate = engine.createTemplate(new 
File("${projectBaseDir}/glv/Gremlin.Net.Template.nuspec.template")).make(["projectVersion":versionToUse])
 def nuspecFile = new 
File("${projectBaseDir}/src/Gremlin.Net.Template/Gremlin.Net.Template.nuspec")
-nuspecFile.newWriter().withWriter{ it << nuspecTemplate }
\ No newline at end of file
+nuspecFile.newWriter().withWriter{ it << nuspecTemplate }

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/TP.cs
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/TP.cs 
b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/TP.cs
new file mode 100644
index 0000000..ac6415d
--- /dev/null
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/TP.cs
@@ -0,0 +1,96 @@
+#region License
+
+/*
+ * 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.
+ */
+
+#endregion
+
+// THIS IS A GENERATED FILE - DO NOT MODIFY THIS FILE DIRECTLY - see pom.xml
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Gremlin.Net.Process.Traversal
+{
+#pragma warning disable 1591
+
+    /// <summary>
+    ///     A <see cref="TP" /> is a predicate of the form Func&lt;string, 
bool&gt;.
+    ///     That is, given some string, return true or false.
+    /// </summary>
+    public class TP : P
+    {
+        /// <summary>
+        ///     Initializes a new instance of the <see cref="P" /> class.
+        /// </summary>
+        /// <param name="operatorName">The name of the predicate.</param>
+        /// <param name="value">The value of the predicate.</param>
+        /// <param name="other">An optional other predicate that is used as an 
argument for this predicate.</param>
+        public TP(string operatorName, string value, P other = null) : 
base(operatorName, value, other)
+        {
+        }
+
+
+        public static TP Absent(string value)
+        {
+            return new TP("absent", value);
+        }
+
+        public static TP Contains(string value)
+        {
+            return new TP("contains", value);
+        }
+
+        public static TP EndsNotWith(string value)
+        {
+            return new TP("endsNotWith", value);
+        }
+
+        public static TP EndsWith(string value)
+        {
+            return new TP("endsWith", value);
+        }
+
+        public static TP StartsNotWith(string value)
+        {
+            return new TP("startsNotWith", value);
+        }
+
+        public static TP StartsWith(string value)
+        {
+            return new TP("startsWith", value);
+        }
+
+
+        private static T[] ToGenericArray<T>(ICollection<T> collection)
+        {
+            return collection?.ToArray() ?? new T[0];
+        }
+
+        /// <inheritdoc />
+        public override string ToString()
+        {
+            return Other == null ? $"{OperatorName}({Value})" : 
$"{OperatorName}({Value},{Other})";
+        }
+    }
+
+#pragma warning restore 1591
+}

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONWriter.cs
----------------------------------------------------------------------
diff --git 
a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONWriter.cs 
b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONWriter.cs
index 9349e57..3178d21 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONWriter.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONWriter.cs
@@ -58,6 +58,7 @@ namespace Gremlin.Net.Structure.IO.GraphSON
                 {typeof(Type), new ClassSerializer()},
                 {typeof(EnumWrapper), new EnumSerializer()},
                 {typeof(P), new PSerializer()},
+                {typeof(TP), new TPSerializer()},
                 {typeof(Vertex), new VertexSerializer()},
                 {typeof(Edge), new EdgeSerializer()},
                 {typeof(Property), new PropertySerializer()},
@@ -163,4 +164,4 @@ namespace Gremlin.Net.Structure.IO.GraphSON
                 yield return ToDict(e);
         }
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/TPSerializer.cs
----------------------------------------------------------------------
diff --git 
a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/TPSerializer.cs 
b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/TPSerializer.cs
new file mode 100644
index 0000000..22ed358
--- /dev/null
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/TPSerializer.cs
@@ -0,0 +1,45 @@
+#region License
+
+/*
+ * 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.
+ */
+
+#endregion
+
+using System.Collections.Generic;
+using Gremlin.Net.Process.Traversal;
+
+namespace Gremlin.Net.Structure.IO.GraphSON
+{
+    internal class TPSerializer : IGraphSONSerializer
+    {
+        public Dictionary<string, dynamic> Dictify(dynamic predicate, 
GraphSONWriter writer)
+        {
+            TP p = predicate;
+            var value = p.Other == null
+                ? writer.ToDict(p.Value)
+                : new List<dynamic> {writer.ToDict(p.Value), 
writer.ToDict(p.Other)};
+            var dict = new Dictionary<string, dynamic>
+            {
+                {"predicate", p.OperatorName},
+                {"value", value}
+            };
+            return GraphSONUtil.ToTypedValue("TP", dict);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslator.java
----------------------------------------------------------------------
diff --git 
a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslator.java
 
b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslator.java
index 48ba882..77e2583 100644
--- 
a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslator.java
+++ 
b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslator.java
@@ -23,6 +23,7 @@ import 
org.apache.commons.configuration.ConfigurationConverter;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
+import org.apache.tinkerpop.gremlin.process.traversal.TP;
 import org.apache.tinkerpop.gremlin.process.traversal.Translator;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
@@ -198,6 +199,7 @@ public final class GroovyTranslator implements 
Translator.ScriptTranslator {
     }
 
     private StringBuilder convertPToString(final P p, final StringBuilder 
current) {
+        if (p instanceof TP) return convertTPToString((TP) p, current);
         if (p instanceof ConnectiveP) {
             final List<P<?>> list = ((ConnectiveP) p).getPredicates();
             for (int i = 0; i < list.size(); i++) {
@@ -210,4 +212,9 @@ public final class GroovyTranslator implements 
Translator.ScriptTranslator {
             
current.append("P.").append(p.getBiPredicate().toString()).append("(").append(convertToString(p.getValue())).append(")");
         return current;
     }
+
+    private StringBuilder convertTPToString(final TP p, final StringBuilder 
current) {
+        
current.append("TP.").append(p.getBiPredicate().toString()).append("(").append(convertToString(p.getValue())).append(")");
+        return current;
+    }
 }

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-javascript/glv/TraversalSource.template
----------------------------------------------------------------------
diff --git a/gremlin-javascript/glv/TraversalSource.template 
b/gremlin-javascript/glv/TraversalSource.template
index ffe0fbc..14df95b 100644
--- a/gremlin-javascript/glv/TraversalSource.template
+++ b/gremlin-javascript/glv/TraversalSource.template
@@ -162,6 +162,48 @@ function createP(operator, args) {
   return new (Function.prototype.bind.apply(P, args));
 }
 
+class TP {
+  /**
+   * Represents an operation.
+   * @constructor
+   */
+  constructor(operator, value, other) {
+    this.operator = operator;
+    this.value = value;
+    this.other = other;
+  }
+
+  /**
+   * Returns the string representation of the instance.
+   * @returns {string}
+   */
+  toString() {
+    if (this.other === undefined) {
+      return this.operator + '(' + this.value + ')';
+    }
+    return this.operator + '(' + this.value + ', ' + this.other + ')';
+  }
+
+  and(arg) {
+    return new P('and', this, arg);
+  }
+
+  or(arg) {
+    return new P('or', this, arg);
+  }
+<% tpmethods.each{ method -> %>
+  /** @param {...Object} args */
+  static <%= toJs.call(method) %>(...args) {
+    return createTP('<%= method %>', args);
+  }
+<% } %>
+}
+
+function createTP(operator, args) {
+  args.unshift(null, operator);
+  return new (Function.prototype.bind.apply(TP, args));
+}
+
 class Traverser {
   constructor(object, bulk) {
     this.object = object;
@@ -199,6 +241,7 @@ class EnumValue {
 module.exports = {
   EnumValue,
   P,
+  TP,
   IO,
   Traversal,
   TraversalSideEffects,
@@ -208,4 +251,4 @@ enums.each{ enumClass ->
         enumClass.getEnumConstants().sort { a, b -> a.name() <=> b.name() 
}.collect { toJs.call(it.name()) }.join(' ') + "')"
 }
 %>
-};
\ No newline at end of file
+};

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-javascript/glv/generate.groovy
----------------------------------------------------------------------
diff --git a/gremlin-javascript/glv/generate.groovy 
b/gremlin-javascript/glv/generate.groovy
index aab55d4..243c607 100644
--- a/gremlin-javascript/glv/generate.groovy
+++ b/gremlin-javascript/glv/generate.groovy
@@ -28,6 +28,7 @@ import 
org.apache.tinkerpop.gremlin.process.traversal.TraversalSource
 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.P
+import org.apache.tinkerpop.gremlin.process.traversal.TP
 import org.apache.tinkerpop.gremlin.process.traversal.IO
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__
 import java.lang.reflect.Modifier
@@ -69,6 +70,12 @@ def binding = ["enums": CoreImports.getClassImports()
                        collect { it.name }.
                        unique().
                        sort { a, b -> a <=> b },
+               "tpmethods": TP.class.getMethods().
+                       findAll { Modifier.isStatic(it.getModifiers()) }.
+                       findAll { TP.class.isAssignableFrom(it.returnType) }.
+                       collect { it.name }.
+                       unique().
+                       sort { a, b -> a <=> b },
                "sourceStepMethods": GraphTraversalSource.getMethods(). // 
SOURCE STEPS
                        findAll { 
GraphTraversalSource.class.equals(it.returnType) }.
                        findAll {

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js
----------------------------------------------------------------------
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js
index c2e810d..b15ecbc 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js
@@ -49,6 +49,7 @@ module.exports = {
     Bytecode: Bytecode,
     EnumValue: t.EnumValue,
     P: t.P,
+    TP: t.TP,
     Traversal: t.Traversal,
     TraversalSideEffects: t.TraversalSideEffects,
     TraversalStrategies: strategiesModule.TraversalStrategies,

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
----------------------------------------------------------------------
diff --git 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
index 2b9ba26..50a7a3e 100644
--- 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
+++ 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
@@ -310,6 +310,66 @@ function createP(operator, args) {
   return new (Function.prototype.bind.apply(P, args));
 }
 
+class TP {
+  /**
+   * Represents an operation.
+   * @constructor
+   */
+  constructor(operator, value, other) {
+    this.operator = operator;
+    this.value = value;
+    this.other = other;
+  }
+
+  /**
+   * Returns the string representation of the instance.
+   * @returns {string}
+   */
+  toString() {
+    if (this.other === undefined) {
+      return this.operator + '(' + this.value + ')';
+    }
+    return this.operator + '(' + this.value + ', ' + this.other + ')';
+  }
+
+
+  /** @param {...Object} args */
+  static absent(...args) {
+    return createTP('absent', args);
+  }
+
+  /** @param {...Object} args */
+  static contains(...args) {
+    return createTP('contains', args);
+  }
+
+  /** @param {...Object} args */
+  static endsNotWith(...args) {
+    return createTP('endsNotWith', args);
+  }
+
+  /** @param {...Object} args */
+  static endsWith(...args) {
+    return createTP('endsWith', args);
+  }
+
+  /** @param {...Object} args */
+  static startsNotWith(...args) {
+    return createTP('startsNotWith', args);
+  }
+
+  /** @param {...Object} args */
+  static startsWith(...args) {
+    return createTP('startsWith', args);
+  }
+
+}
+
+function createTP(operator, args) {
+  args.unshift(null, operator);
+  return new (Function.prototype.bind.apply(TP, args));
+}
+
 class Traverser {
   constructor(object, bulk) {
     this.object = object;
@@ -347,6 +407,7 @@ class EnumValue {
 module.exports = {
   EnumValue,
   P,
+  TP,
   IO,
   Traversal,
   TraversalSideEffects,
@@ -363,4 +424,4 @@ module.exports = {
   pop: toEnum('Pop', 'all first last mixed'),
   scope: toEnum('Scope', 'global local'),
   t: toEnum('T', 'id key label value')
-};
\ No newline at end of file
+};

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/type-serializers.js
----------------------------------------------------------------------
diff --git 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/type-serializers.js
 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/type-serializers.js
index 0e17cc8..ca81c2a 100644
--- 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/type-serializers.js
+++ 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/type-serializers.js
@@ -150,6 +150,28 @@ class PSerializer extends TypeSerializer {
   }
 }
 
+class TPSerializer extends TypeSerializer {
+  /** @param {TP} item */
+  serialize(item) {
+    const result = {};
+    result[typeKey] = 'g:TP';
+    const resultValue = result[valueKey] = {
+      'predicate': item.operator
+    };
+    if (item.other === undefined || item.other === null) {
+      resultValue['value'] = this.writer.adaptObject(item.value);
+    }
+    else {
+      resultValue['value'] = [ this.writer.adaptObject(item.value), 
this.writer.adaptObject(item.other) ];
+    }
+    return result;
+  }
+
+  canBeUsedFor(value) {
+    return (value instanceof t.TP);
+  }
+}
+
 class LambdaSerializer extends TypeSerializer {
   /** @param {Function} item */
   serialize(item) {
@@ -391,4 +413,4 @@ module.exports = {
   valueKey,
   VertexPropertySerializer,
   VertexSerializer
-};
\ No newline at end of file
+};

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
----------------------------------------------------------------------
diff --git 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
index a0ddddb..abef2a2 100644
--- 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
+++ 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
@@ -218,6 +218,7 @@ function getSandbox(g, parameters) {
     },
     Order: traversalModule.order,
     P: traversalModule.P,
+    TP: traversalModule.TP,
     IO: traversalModule.IO,
     Pick: traversalModule.pick,
     Pop: traversalModule.pop,

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-python/glv/TraversalSource.template
----------------------------------------------------------------------
diff --git a/gremlin-python/glv/TraversalSource.template 
b/gremlin-python/glv/TraversalSource.template
index 713681b..00a5934 100644
--- a/gremlin-python/glv/TraversalSource.template
+++ b/gremlin-python/glv/TraversalSource.template
@@ -152,6 +152,25 @@ def <%= method %>(*args):
 statics.add_static('<%= method %>',<%= method %>)
 <% } %>
 
+class TP(P):
+    def __init__(self, operator, value, other=None):
+        P.__init__(self, operator, value, other)
+<% tpmethods.each { method -> %>
+    @staticmethod
+    def <%= method %>(*args):
+        return TP("<%= toJava.call(method) %>", *args)
+<% } %>
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and self.operator == 
other.operator and self.value == other.value and self.other == other.other
+
+    def __repr__(self):
+        return self.operator + "(" + str(self.value) + ")" if self.other is 
None else self.operator + "(" + str(self.value) + "," + str(self.other) + ")"
+<% tpmethods.findAll{!it.equals("clone")}.each { method -> %>
+def <%= method %>(*args):
+    return TP.<%= method %>(*args)
+statics.add_static('<%= method %>',<%= method %>)
+<% } %>
+
 <% tokens.each { k,v -> %>
 '''
 <%= k %>
@@ -324,4 +343,4 @@ class Binding(object):
         return hash(self.key) + hash(self.value)
 
     def __repr__(self):
-        return "binding[" + self.key + "=" + str(self.value) + "]"
\ No newline at end of file
+        return "binding[" + self.key + "=" + str(self.value) + "]"

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-python/glv/generate.groovy
----------------------------------------------------------------------
diff --git a/gremlin-python/glv/generate.groovy 
b/gremlin-python/glv/generate.groovy
index c7ad241..8c3d647 100644
--- a/gremlin-python/glv/generate.groovy
+++ b/gremlin-python/glv/generate.groovy
@@ -26,6 +26,7 @@ import 
org.apache.tinkerpop.gremlin.process.traversal.TraversalSource
 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.P
+import org.apache.tinkerpop.gremlin.process.traversal.TP
 import org.apache.tinkerpop.gremlin.process.traversal.IO
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__
 import java.lang.reflect.Modifier
@@ -63,6 +64,12 @@ def binding = ["enums": CoreImports.getClassImports()
                        collect { toPython(it.name) }.
                        unique().
                        sort { a, b -> a <=> b },
+               "tpmethods": TP.class.getMethods().
+                       findAll { Modifier.isStatic(it.getModifiers()) }.
+                       findAll { TP.class.isAssignableFrom(it.returnType) }.
+                       collect { toPython(it.name) }.
+                       unique().
+                       sort { a, b -> a <=> b },
                "sourceStepMethods": GraphTraversalSource.getMethods(). // 
SOURCE STEPS
                        findAll { 
GraphTraversalSource.class.equals(it.returnType) }.
                        findAll {
@@ -102,4 +109,4 @@ traversalFile.newWriter().withWriter{ it << 
traversalTemplate }
 
 def graphTraversalTemplate = engine.createTemplate(new 
File("${projectBaseDir}/glv/GraphTraversalSource.template")).make(binding)
 def graphTraversalFile = new 
File("${projectBaseDir}/src/main/jython/gremlin_python/process/graph_traversal.py")
-graphTraversalFile.newWriter().withWriter{ it << graphTraversalTemplate }
\ No newline at end of file
+graphTraversalFile.newWriter().withWriter{ it << graphTraversalTemplate }

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-python/src/main/java/org/apache/tinkerpop/gremlin/python/jsr223/PythonTranslator.java
----------------------------------------------------------------------
diff --git 
a/gremlin-python/src/main/java/org/apache/tinkerpop/gremlin/python/jsr223/PythonTranslator.java
 
b/gremlin-python/src/main/java/org/apache/tinkerpop/gremlin/python/jsr223/PythonTranslator.java
index 0bc324e..0d2695a 100644
--- 
a/gremlin-python/src/main/java/org/apache/tinkerpop/gremlin/python/jsr223/PythonTranslator.java
+++ 
b/gremlin-python/src/main/java/org/apache/tinkerpop/gremlin/python/jsr223/PythonTranslator.java
@@ -24,6 +24,7 @@ import 
org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 import org.apache.tinkerpop.gremlin.process.traversal.Operator;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
+import org.apache.tinkerpop.gremlin.process.traversal.TP;
 import org.apache.tinkerpop.gremlin.process.traversal.Translator;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
@@ -224,6 +225,7 @@ public class PythonTranslator implements 
Translator.ScriptTranslator {
     }
 
     private StringBuilder convertPToString(final P p, final StringBuilder 
current) {
+        if (p instanceof TP) return convertTPToString((TP) p, current);
         if (p instanceof ConnectiveP) {
             final List<P<?>> list = ((ConnectiveP) p).getPredicates();
             for (int i = 0; i < list.size(); i++) {
@@ -237,6 +239,11 @@ public class PythonTranslator implements 
Translator.ScriptTranslator {
         return current;
     }
 
+    private StringBuilder convertTPToString(final TP p, final StringBuilder 
current) {
+        
current.append(convertStatic("TP.")).append(p.getBiPredicate().toString()).append("(").append(convertToString(p.getValue())).append(")");
+        return current;
+    }
+
     protected String convertLambdaToString(final Lambda lambda) {
         final String lambdaString = lambda.getLambdaScript().trim();
         return lambdaString.startsWith("lambda") ? lambdaString : "lambda " + 
lambdaString;

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-python/src/main/jython/gremlin_python/process/traversal.py
----------------------------------------------------------------------
diff --git a/gremlin-python/src/main/jython/gremlin_python/process/traversal.py 
b/gremlin-python/src/main/jython/gremlin_python/process/traversal.py
index 094bd6d..4892bfc 100644
--- a/gremlin-python/src/main/jython/gremlin_python/process/traversal.py
+++ b/gremlin-python/src/main/jython/gremlin_python/process/traversal.py
@@ -322,6 +322,67 @@ def without(*args):
 statics.add_static('without',without)
 
 
+class TP(object):
+    def __init__(self, operator, value, other=None):
+        self.operator = operator
+        self.value = value
+        self.other = other
+
+    @staticmethod
+    def absent(*args):
+        return TP("absent", *args)
+
+    @staticmethod
+    def contains(*args):
+        return TP("contains", *args)
+
+    @staticmethod
+    def endsNotWith(*args):
+        return TP("endsNotWith", *args)
+
+    @staticmethod
+    def endsWith(*args):
+        return TP("endsWith", *args)
+
+    @staticmethod
+    def startsNotWith(*args):
+        return TP("startsNotWith", *args)
+
+    @staticmethod
+    def startsWith(*args):
+        return TP("startsWith", *args)
+
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and self.operator == 
other.operator and self.value == other.value and self.other == other.other
+
+    def __repr__(self):
+        return self.operator + "(" + str(self.value) + ")" if self.other is 
None else self.operator + "(" + str(self.value) + "," + str(self.other) + ")"
+
+def absent(*args):
+    return TP.absent(*args)
+statics.add_static('absent',absent)
+
+def contains(*args):
+    return TP.contains(*args)
+statics.add_static('contains',contains)
+
+def endsNotWith(*args):
+    return TP.endsNotWith(*args)
+statics.add_static('endsNotWith',endsNotWith)
+
+def endsWith(*args):
+    return TP.endsWith(*args)
+statics.add_static('endsWith',endsWith)
+
+def startsNotWith(*args):
+    return TP.startsNotWith(*args)
+statics.add_static('startsNotWith',startsNotWith)
+
+def startsWith(*args):
+    return TP.startsWith(*args)
+statics.add_static('startsWith',startsWith)
+
+
 
 '''
 IO
@@ -556,4 +617,4 @@ class Binding(object):
         return hash(self.key) + hash(self.value)
 
     def __repr__(self):
-        return "binding[" + self.key + "=" + str(self.value) + "]"
\ No newline at end of file
+        return "binding[" + self.key + "=" + str(self.value) + "]"

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV2d0.py
----------------------------------------------------------------------
diff --git 
a/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV2d0.py 
b/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV2d0.py
index d53a080..2cb0b5b 100644
--- a/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV2d0.py
+++ b/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV2d0.py
@@ -27,7 +27,7 @@ from aenum import Enum
 
 from gremlin_python import statics
 from gremlin_python.statics import FloatType, FunctionType, IntType, LongType, 
TypeType
-from gremlin_python.process.traversal import Binding, Bytecode, P, Traversal, 
Traverser, TraversalStrategy
+from gremlin_python.process.traversal import Binding, Bytecode, P, TP, 
Traversal, Traverser, TraversalStrategy
 from gremlin_python.structure.graph import Edge, Property, Vertex, 
VertexProperty, Path
 
 # When we fall back to a superclass's serializer, we iterate over this map.
@@ -278,6 +278,17 @@ class PSerializer(_GraphSONTypeIO):
         return GraphSONUtil.typedValue("P", out)
 
 
+class TPSerializer(_GraphSONTypeIO):
+    python_type = TP
+
+    @classmethod
+    def dictify(cls, p, writer):
+        out = {"predicate": p.operator,
+               "value": [writer.toDict(p.value), writer.toDict(p.other)] if 
p.other is not None else
+               writer.toDict(p.value)}
+        return GraphSONUtil.typedValue("TP", out)
+
+
 class BindingSerializer(_GraphSONTypeIO):
     python_type = Binding
 

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV3d0.py
----------------------------------------------------------------------
diff --git 
a/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV3d0.py 
b/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV3d0.py
index 5539448..266e23d 100644
--- a/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV3d0.py
+++ b/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV3d0.py
@@ -27,7 +27,7 @@ from aenum import Enum
 
 from gremlin_python import statics
 from gremlin_python.statics import FloatType, FunctionType, IntType, LongType, 
TypeType, DictType, ListType, SetType
-from gremlin_python.process.traversal import Binding, Bytecode, P, Traversal, 
Traverser, TraversalStrategy, T
+from gremlin_python.process.traversal import Binding, Bytecode, P, TP, 
Traversal, Traverser, TraversalStrategy, T
 from gremlin_python.structure.graph import Edge, Property, Vertex, 
VertexProperty, Path
 
 # When we fall back to a superclass's serializer, we iterate over this map.
@@ -284,6 +284,17 @@ class PSerializer(_GraphSONTypeIO):
         return GraphSONUtil.typedValue("P", out)
 
 
+class TPSerializer(_GraphSONTypeIO):
+    python_type = TP
+
+    @classmethod
+    def dictify(cls, p, writer):
+        out = {"predicate": p.operator,
+               "value": [writer.toDict(p.value), writer.toDict(p.other)] if 
p.other is not None else
+               writer.toDict(p.value)}
+        return GraphSONUtil.typedValue("TP", out)
+
+
 class BindingSerializer(_GraphSONTypeIO):
     python_type = Binding
 

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-python/src/main/jython/radish/feature_steps.py
----------------------------------------------------------------------
diff --git a/gremlin-python/src/main/jython/radish/feature_steps.py 
b/gremlin-python/src/main/jython/radish/feature_steps.py
index aff73dc..151d6d5 100644
--- a/gremlin-python/src/main/jython/radish/feature_steps.py
+++ b/gremlin-python/src/main/jython/radish/feature_steps.py
@@ -21,7 +21,7 @@ import json
 import re
 from gremlin_python.structure.graph import Graph, Path
 from gremlin_python.process.graph_traversal import __
-from gremlin_python.process.traversal import Barrier, Cardinality, P, Pop, 
Scope, Column, Order, Direction, T, Pick, Operator, IO
+from gremlin_python.process.traversal import Barrier, Cardinality, P, TP, Pop, 
Scope, Column, Order, Direction, T, Pick, Operator, IO
 from radish import given, when, then
 from hamcrest import *
 
@@ -256,6 +256,7 @@ def _make_traversal(g, traversal_string, params):
          "Direction": Direction,
          "Order": Order,
          "P": P,
+         "TP": TP,
          "IO": IO,
          "Pick": Pick,
          "Pop": Pop,

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-test/features/filter/Has.feature
----------------------------------------------------------------------
diff --git a/gremlin-test/features/filter/Has.feature 
b/gremlin-test/features/filter/Has.feature
index ddf9984..9d2bf4f 100644
--- a/gremlin-test/features/filter/Has.feature
+++ b/gremlin-test/features/filter/Has.feature
@@ -557,4 +557,59 @@ Feature: Step - has()
     When iterated to list
     Then the result should be unordered
       | result |
-      | d[6].l |
\ No newline at end of file
+      | d[6].l |
+
+  Scenario: g_V_hasXname_containsXarkXX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().has("name", TP.contains("ark"))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | v[marko] |
+
+  Scenario: g_V_hasXname_startsWithXmarXX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().has("name", TP.startsWith("mar"))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | v[marko] |
+
+  Scenario: g_V_hasXname_endsWithXasXX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().has("name", TP.endsWith("as"))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | v[vadas] |
+
+  Scenario: g_V_hasXperson_name_containsXoX_andXltXmXXX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().has("person", "name", TP.contains("o").and(P.lt("m")))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | v[josh] |
+
+  Scenario: g_V_hasXname_gtXmX_andXcontainsXoXXX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().has("name", P.gt("m").and(TP.contains("o")))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | v[marko] |

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/9d83f8af/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/HasTest.java
----------------------------------------------------------------------
diff --git 
a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/HasTest.java
 
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/HasTest.java
index 3bdb24a..cb4abab 100644
--- 
a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/HasTest.java
+++ 
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/HasTest.java
@@ -23,6 +23,7 @@ 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.P;
+import org.apache.tinkerpop.gremlin.process.traversal.TP;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
 import org.apache.tinkerpop.gremlin.structure.Edge;
@@ -125,6 +126,16 @@ public abstract class HasTest extends 
AbstractGremlinProcessTest {
 
     public abstract Traversal<Vertex, Long> 
get_g_V_hasXage_withoutX27_29X_count();
 
+    public abstract Traversal<Vertex, Vertex> 
get_g_V_hasXname_containsXarkXX();
+
+    public abstract Traversal<Vertex,Vertex> 
get_g_V_hasXname_startsWithXmarXX();
+
+    public abstract Traversal<Vertex,Vertex> get_g_V_hasXname_endsWithXasXX();
+
+    public abstract Traversal<Vertex,Vertex> 
get_g_V_hasXperson_name_containsXoX_andXltXmXXX();
+
+    public abstract Traversal<Vertex,Vertex> 
get_g_V_hasXname_gtXmX_andXcontainsXoXXX();
+
     @Test
     @LoadGraphWith(MODERN)
     public void g_V_outXcreatedX_hasXname__mapXlengthX_isXgtX3XXX_name() {
@@ -547,6 +558,56 @@ public abstract class HasTest extends 
AbstractGremlinProcessTest {
         assertEquals(2L, traversal.next().longValue());
     }
 
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_V_hasXname_containsXarkXX() {
+        final Traversal<Vertex, Vertex> traversal = 
get_g_V_hasXname_containsXarkXX();
+        printTraversalForm(traversal);
+        assertTrue(traversal.hasNext());
+        assertTrue(traversal.next().value("name").equals("marko"));
+        assertFalse(traversal.hasNext());
+    }
+
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_V_hasXname_startsWithXmarXX() {
+        final Traversal<Vertex, Vertex> traversal = 
get_g_V_hasXname_startsWithXmarXX();
+        printTraversalForm(traversal);
+        assertTrue(traversal.hasNext());
+        assertTrue(traversal.next().value("name").equals("marko"));
+        assertFalse(traversal.hasNext());
+    }
+
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_V_hasXname_endsWithXasXX() {
+        final Traversal<Vertex, Vertex> traversal = 
get_g_V_hasXname_endsWithXasXX();
+        printTraversalForm(traversal);
+        assertTrue(traversal.hasNext());
+        assertTrue(traversal.next().value("name").equals("vadas"));
+        assertFalse(traversal.hasNext());
+    }
+
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_V_hasXperson_name_containsXoX_andXltXmXXX() {
+        final Traversal<Vertex, Vertex> traversal = 
get_g_V_hasXperson_name_containsXoX_andXltXmXXX();
+        printTraversalForm(traversal);
+        assertTrue(traversal.hasNext());
+        assertTrue(traversal.next().value("name").equals("josh"));
+        assertFalse(traversal.hasNext());
+    }
+
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_V_hasXname_gtXmX_andXcontainsXoXXX() {
+        final Traversal<Vertex, Vertex> traversal = 
get_g_V_hasXname_gtXmX_andXcontainsXoXXX();
+        printTraversalForm(traversal);
+        assertTrue(traversal.hasNext());
+        assertTrue(traversal.next().value("name").equals("marko"));
+        assertFalse(traversal.hasNext());
+    }
+
     public static class Traversals extends HasTest {
         @Override
         public Traversal<Edge, Edge> get_g_EX11X_outV_outE_hasXid_10X(final 
Object e11Id, final Object e10Id) {
@@ -727,5 +788,30 @@ public abstract class HasTest extends 
AbstractGremlinProcessTest {
         public Traversal<Vertex, Long> get_g_V_hasXage_withoutX27_29X_count() {
             return g.V().has("age", P.without(27, 29)).count();
         }
+
+        @Override
+        public Traversal<Vertex, Vertex> get_g_V_hasXname_containsXarkXX() {
+            return g.V().has("name", TP.contains("ark"));
+        }
+
+        @Override
+        public Traversal<Vertex, Vertex> get_g_V_hasXname_startsWithXmarXX() {
+            return g.V().has("name", TP.startsWith("mar"));
+        }
+
+        @Override
+        public Traversal<Vertex, Vertex> get_g_V_hasXname_endsWithXasXX() {
+            return g.V().has("name", TP.endsWith("as"));
+        }
+
+        @Override
+        public Traversal<Vertex, Vertex> 
get_g_V_hasXperson_name_containsXoX_andXltXmXXX() {
+            return g.V().has("person","name", TP.contains("o").and(P.lt("m")));
+        }
+
+        @Override
+        public Traversal<Vertex, Vertex> 
get_g_V_hasXname_gtXmX_andXcontainsXoXXX() {
+            return g.V().has("name", P.gt("m").and(TP.contains("o")));
+        }
     }
 }
\ No newline at end of file

Reply via email to