This is an automated email from the ASF dual-hosted git repository. kenhuuu pushed a commit to branch 3.8-dev in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 0e743cbc82e3cb9eeb7050ec11e17e701b2263dd Author: Guian Gumpac <[email protected]> AuthorDate: Mon Aug 31 16:30:15 2026 -0700 Fixed P.typeOf() Assisted-by: Claude Opus 5 --- .../structure/io/binary/types/PSerializer.java | 7 +- .../io/graphson/TraversalSerializersV2.java | 4 + .../io/graphson/TraversalSerializersV3.java | 4 + .../gremlin/util/ser/AbstractRoundTripTest.java | 12 +++ .../util/ser/binary/types/PSerializerTest.java | 85 ++++++++++++++++++++++ 5 files changed, 111 insertions(+), 1 deletion(-) diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/PSerializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/PSerializer.java index 0d123288a4..a1edf6cbdb 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/PSerializer.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/PSerializer.java @@ -172,7 +172,12 @@ public class PSerializer<T extends P> extends SimpleTypeSerializer<T> { context.writeValue(length, buffer, false); for (Object o : argsAsList) { - context.write(o, buffer); + // typeOf's argument travels as the class's simple name, so the read resolves it against + // CompareType.GlobalTypeCache. + if ("typeOf".equals(predicateName) && o instanceof Class) + context.write(((Class<?>) o).getSimpleName(), buffer); + else + context.write(o, buffer); } } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2.java index 7035ec8105..9daf43f80b 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2.java @@ -165,6 +165,10 @@ final class TraversalSerializersV2 { jsonGenerator.writeObject(object); } jsonGenerator.writeEndArray(); + } else if ("typeOf".equals(p.getPredicateName()) && p.getValue() instanceof Class) { + // as in binary/types/PSerializer, typeOf's Class argument travels as the simple name so the read + // resolves it against CompareType.GlobalTypeCache. + jsonGenerator.writeObjectField(GraphSONTokens.VALUE, ((Class<?>) p.getValue()).getSimpleName()); } else jsonGenerator.writeObjectField(GraphSONTokens.VALUE, p.getValue()); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3.java index ec98331826..2191e2654e 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3.java @@ -165,6 +165,10 @@ final class TraversalSerializersV3 { jsonGenerator.writeEndArray(); } else if (p instanceof NotP) { jsonGenerator.writeObjectField(GraphSONTokens.VALUE, p.negate()); + } else if ("typeOf".equals(p.getPredicateName()) && p.getValue() instanceof Class) { + // as in binary/types/PSerializer, typeOf's Class argument travels as the simple name so the read + // resolves it against CompareType.GlobalTypeCache. + jsonGenerator.writeObjectField(GraphSONTokens.VALUE, ((Class<?>) p.getValue()).getSimpleName()); } else jsonGenerator.writeObjectField(GraphSONTokens.VALUE, p.getValue()); diff --git a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/AbstractRoundTripTest.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/AbstractRoundTripTest.java index 259dacf0ff..98280351db 100644 --- a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/AbstractRoundTripTest.java +++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/AbstractRoundTripTest.java @@ -22,6 +22,7 @@ import org.apache.tinkerpop.gremlin.process.computer.Computer; import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.decoration.VertexProgramStrategy; import org.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser; import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; +import org.apache.tinkerpop.gremlin.process.traversal.GType; import org.apache.tinkerpop.gremlin.process.traversal.Operator; import org.apache.tinkerpop.gremlin.process.traversal.Order; import org.apache.tinkerpop.gremlin.process.traversal.P; @@ -92,6 +93,7 @@ import java.util.function.Consumer; import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.hasLabel; import static org.apache.tinkerpop.gremlin.util.MockitoHamcrestMatcherAdapter.reflectionEquals; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; @RunWith(Parameterized.class) @@ -159,6 +161,13 @@ public abstract class AbstractRoundTripTest { final DefaultTraversalMetrics traversalMetrics = new DefaultTraversalMetrics(666, nestedMetrics); final DefaultTraversalMetrics emptyTraversalMetrics = new DefaultTraversalMetrics(444, Collections.emptyList()); + // the three P.typeOf rows assert by evaluating the decoded predicate, since the Class form arrives as String + final Consumer<P> typeOfBoolean = p -> { + assertThat(p.test(Boolean.FALSE), is(true)); + assertThat(p.test(Boolean.TRUE), is(true)); + assertThat(p.test("false"), is(false)); + }; + return Arrays.asList( new Object[] {"String", "ABC", null}, new Object[] {"Char", '£', null}, @@ -232,6 +241,9 @@ public abstract class AbstractRoundTripTest { new Object[] {"Pnot", P.not(P.lte(1)), null}, new Object[] {"Pwithout", P.without(1,2,3,4,null), null}, new Object[] {"Pinside", P.inside(0.0d, 0.6d), null}, + new Object[] {"PtypeOfGType", P.typeOf(GType.BOOLEAN), typeOfBoolean}, + new Object[] {"PtypeOfString", P.typeOf("Boolean"), typeOfBoolean}, + new Object[] {"PtypeOfClass", P.typeOf(Boolean.class), typeOfBoolean}, new Object[] {"TextPstartingWith", TextP.startingWith("mark"), null}, new Object[] {"TextPregex", TextP.regex("^meh"), null}, new Object[] {"TextPnotRegex", TextP.notRegex("^meh"), null}, diff --git a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/types/PSerializerTest.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/types/PSerializerTest.java new file mode 100644 index 0000000000..110af5c218 --- /dev/null +++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/types/PSerializerTest.java @@ -0,0 +1,85 @@ +/* + * 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.util.ser.binary.types; + +import io.netty.buffer.ByteBufAllocator; +import org.apache.tinkerpop.gremlin.process.traversal.CompareType; +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.structure.io.Buffer; +import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryReader; +import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryWriter; +import org.apache.tinkerpop.gremlin.util.ser.NettyBufferFactory; +import org.junit.Test; + +import java.io.IOException; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsNot.not; +import static org.hamcrest.core.StringContains.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * GraphBinary encoding details for {@code P.typeOf(Class)}. Format-agnostic round trips of all three + * {@code P.typeOf} overloads are rows in {@code AbstractRoundTripTest}, which drives GraphBinary and GraphSON. + */ +public class PSerializerTest { + + private static final NettyBufferFactory bufferFactory = new NettyBufferFactory(); + + private final ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; + + /** + * The {@code Class} form decodes to {@code String}, so deliberately not {@code equals} to what was written. + */ + @Test + public void shouldWriteTypeOfClassAsSimpleName() throws Exception { + assertEquals(P.typeOf("Boolean"), roundTrip(P.typeOf(Boolean.class))); + } + + /** + * The refusal comes from {@code GlobalTypeCache} on evaluation, not from {@code ClassRegistry} on read. + */ + @Test + public void shouldFailOnUnregisteredClassWithGlobalTypeCacheMessage() throws Exception { + final P<Object> read = roundTrip(P.typeOf(UnregisteredType.class)); + + try { + read.test("anything"); + fail("A class that GlobalTypeCache does not hold must not evaluate"); + } catch (IllegalArgumentException ex) { + assertThat(ex.getMessage(), containsString("is not a registered type")); + assertThat(ex.getMessage(), containsString(UnregisteredType.class.getSimpleName())); + assertThat(ex.getMessage(), not(containsString("Class not recognized"))); + } + } + + private P<Object> roundTrip(final P<Object> p) throws IOException { + final Buffer buffer = bufferFactory.create(allocator.buffer()); + new GraphBinaryWriter().write(p, buffer); + + return (P<Object>) new GraphBinaryReader().read(buffer); + } + + /** + * A class {@link CompareType.GlobalTypeCache} does not hold. Never register it. + */ + private static final class UnregisteredType { + } +}
