This is an automated email from the ASF dual-hosted git repository. Cole-Greer pushed a commit to branch simplePDT in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit c77d1d694e8beed17281cf74a4bbd5bda66ad171 Author: Cole Greer <[email protected]> AuthorDate: Wed Jun 24 13:25:43 2026 -0700 Add PrimitivePDTAdapter, registry hydration, and binary reader/writer dispatch Wires PrimitivePDT into the registry and the GraphBinary read/write paths. - New PrimitivePDTAdapter<T> extends ProviderDefinedTypeAdapter<T> with toValue(T)/fromValue(String). - ProviderDefinedTypeRegistry: parallel primitiveAdaptersByName/ByClass; register(...) routes PrimitivePDTAdapter into the primitive maps; getPrimitiveAdapterByName/ByClass added. Registering a class already registered under the other kind (composite vs primitive) throws (fail-fast, bidirectional). ServiceLoader discovery on the supertype picks up primitive adapters automatically. - hydratePrimitive(PrimitiveProviderDefinedType): adapter lookup by name + fromValue, with graceful degradation (log + return raw) on missing or throwing adapter. The composite hydrate() recursion now also hydrates a PrimitiveProviderDefinedType nested inside a composite's fields. - GraphBinaryReader.read(): hydrate a deserialized PrimitiveProviderDefinedType via the registry. - GraphBinaryWriter: dehydrate a raw object whose class has a registered PrimitivePDTAdapter into a PrimitiveProviderDefinedType (parallel to the composite adapter path), resolving the PRIMITIVE_PDT serializer. GraphSON, grammar, server fixtures, and GLVs remain for later beads. Tests: registry primitive register/lookup, hydratePrimitive success and graceful degradation, dual-registration throws, primitive-nested-in-composite hydration; writer adapter round-trip for an unannotated primitive type. tinkerpop-2gy.3 Assisted-by: Kiro:claude-opus-4.8 --- .../structure/io/binary/GraphBinaryReader.java | 4 + .../structure/io/binary/GraphBinaryWriter.java | 38 ++++-- .../structure/io/pdt/PrimitivePDTAdapter.java | 28 ++++ .../io/pdt/ProviderDefinedTypeRegistry.java | 52 +++++++- .../io/pdt/ProviderDefinedTypeRegistryTest.java | 141 +++++++++++++++++++++ .../util/ser/binary/GraphBinaryWriterPdtTest.java | 75 +++++++++++ 6 files changed, 327 insertions(+), 11 deletions(-) diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryReader.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryReader.java index 5f3cf240c6..157bea16b9 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryReader.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryReader.java @@ -19,6 +19,7 @@ package org.apache.tinkerpop.gremlin.structure.io.binary; import org.apache.tinkerpop.gremlin.structure.io.Buffer; +import org.apache.tinkerpop.gremlin.structure.io.pdt.PrimitiveProviderDefinedType; import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedType; import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeRegistry; @@ -108,6 +109,9 @@ public class GraphBinaryReader { if (pdtRegistry != null && result instanceof ProviderDefinedType) { return (T) pdtRegistry.hydrate((ProviderDefinedType) result); } + if (pdtRegistry != null && result instanceof PrimitiveProviderDefinedType) { + return (T) pdtRegistry.hydratePrimitive((PrimitiveProviderDefinedType) result); + } return result; } } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriter.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriter.java index 27bc0bda15..7bbdb6d0f6 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriter.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/GraphBinaryWriter.java @@ -18,9 +18,12 @@ */ package org.apache.tinkerpop.gremlin.structure.io.binary; +import org.apache.tinkerpop.gremlin.structure.io.binary.types.PrimitiveProviderDefinedTypeSerializer; import org.apache.tinkerpop.gremlin.structure.io.binary.types.ProviderDefinedTypeSerializer; import org.apache.tinkerpop.gremlin.structure.io.binary.types.TransformSerializer; import org.apache.tinkerpop.gremlin.structure.io.pdt.CompositePDTAdapter; +import org.apache.tinkerpop.gremlin.structure.io.pdt.PrimitivePDTAdapter; +import org.apache.tinkerpop.gremlin.structure.io.pdt.PrimitiveProviderDefinedType; import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedType; import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeAdapter; import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeRegistry; @@ -91,6 +94,10 @@ public class GraphBinaryWriter { serializer.writeValue((T) dehydrateToPdt(value, objectClass), buffer, this, nullable); return; } + if (serializer instanceof PrimitiveProviderDefinedTypeSerializer && !(value instanceof PrimitiveProviderDefinedType)) { + serializer.writeValue((T) dehydrateToPrimitivePdt(value, objectClass), buffer, this, nullable); + return; + } serializer.writeValue(value, buffer, this, nullable); } @@ -108,16 +115,16 @@ public class GraphBinaryWriter { final TypeSerializer<T> serializer = (TypeSerializer<T>) getSerializerOrAdapterFallback(objectClass); if (serializer instanceof ProviderDefinedTypeSerializer && !(value instanceof ProviderDefinedType)) { - // Convert to ProviderDefinedType (via annotation or adapter), then re-enter write(). - // On re-entry, ProviderDefinedType.class is directly registered in the registry, - // and the instanceof guard prevents double-wrapping. write((T) dehydrateToPdt(value, objectClass), buffer); return; } + if (serializer instanceof PrimitiveProviderDefinedTypeSerializer && !(value instanceof PrimitiveProviderDefinedType)) { + write((T) dehydrateToPrimitivePdt(value, objectClass), buffer); + return; + } + if (serializer instanceof TransformSerializer) { - // For historical reasons, there are types that need to be transformed into another type - // before serialization, e.g., Map.Entry final TransformSerializer<T> transformSerializer = (TransformSerializer<T>) serializer; write(transformSerializer.transform(value), buffer); return; @@ -168,15 +175,20 @@ public class GraphBinaryWriter { /** * Attempts to get a serializer for the given class. If no serializer is found and the pdtRegistry - * has an adapter for the class, returns the CompositePDT serializer. + * has an adapter for the class (composite or primitive), returns the appropriate PDT serializer. */ @SuppressWarnings("unchecked") private <DT> TypeSerializer<DT> getSerializerOrAdapterFallback(final Class<?> type) throws IOException { try { return (TypeSerializer<DT>) registry.getSerializer(type); } catch (final IOException e) { - if (pdtRegistry != null && pdtRegistry.getAdapterByClass(type).isPresent()) { - return (TypeSerializer<DT>) registry.getSerializer(DataType.COMPOSITE_PDT); + if (pdtRegistry != null) { + if (pdtRegistry.getAdapterByClass(type).isPresent()) { + return (TypeSerializer<DT>) registry.getSerializer(DataType.COMPOSITE_PDT); + } + if (pdtRegistry.getPrimitiveAdapterByClass(type).isPresent()) { + return (TypeSerializer<DT>) registry.getSerializer(DataType.PRIMITIVE_PDT); + } } throw e; } @@ -205,4 +217,14 @@ public class GraphBinaryWriter { return ProviderDefinedType.from(value); } + /** + * Dehydrates a value to a {@link PrimitiveProviderDefinedType} using a {@link PrimitivePDTAdapter} from the + * pdtRegistry. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + private PrimitiveProviderDefinedType dehydrateToPrimitivePdt(final Object value, final Class<?> objectClass) { + final PrimitivePDTAdapter adapter = (PrimitivePDTAdapter) pdtRegistry.getPrimitiveAdapterByClass(objectClass).get(); + return new PrimitiveProviderDefinedType(adapter.typeName(), adapter.toValue(value)); + } + } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/PrimitivePDTAdapter.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/PrimitivePDTAdapter.java new file mode 100644 index 0000000000..670feb1118 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/PrimitivePDTAdapter.java @@ -0,0 +1,28 @@ +/* + * 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.structure.io.pdt; + +/** + * Adapter for converting between a typed object and a {@link PrimitiveProviderDefinedType} string value. + * Used for single-value (primitive) provider-defined types. + */ +public interface PrimitivePDTAdapter<T> extends ProviderDefinedTypeAdapter<T> { + String toValue(T obj); + T fromValue(String value); +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeRegistry.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeRegistry.java index 4213f4ec94..bb3b7eb3f4 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeRegistry.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeRegistry.java @@ -42,6 +42,8 @@ public final class ProviderDefinedTypeRegistry { private final Map<String, CompositePDTAdapter<?>> adaptersByName = new ConcurrentHashMap<>(); private final Map<Class<?>, CompositePDTAdapter<?>> adaptersByClass = new ConcurrentHashMap<>(); + private final Map<String, PrimitivePDTAdapter<?>> primitiveAdaptersByName = new ConcurrentHashMap<>(); + private final Map<Class<?>, PrimitivePDTAdapter<?>> primitiveAdaptersByClass = new ConcurrentHashMap<>(); private ProviderDefinedTypeRegistry() {} @@ -65,12 +67,24 @@ public final class ProviderDefinedTypeRegistry { } /** - * Registers an adapter. Composite adapters ({@link CompositePDTAdapter}) are stored for - * hydration/dehydration; other adapter kinds are routed to their respective maps in future beads. + * Registers an adapter. Composite adapters ({@link CompositePDTAdapter}) are stored in composite maps; + * primitive adapters ({@link PrimitivePDTAdapter}) are stored in primitive maps. + * + * @throws IllegalArgumentException if the adapter's target class is already registered under the other kind */ public void register(final ProviderDefinedTypeAdapter<?> adapter) { - if (adapter instanceof CompositePDTAdapter) { + if (adapter instanceof PrimitivePDTAdapter) { + final PrimitivePDTAdapter<?> primitive = (PrimitivePDTAdapter<?>) adapter; + if (adaptersByClass.containsKey(primitive.targetClass())) + throw new IllegalArgumentException("Class " + primitive.targetClass().getName() + + " is already registered as a composite PDT adapter"); + primitiveAdaptersByName.put(primitive.typeName(), primitive); + primitiveAdaptersByClass.put(primitive.targetClass(), primitive); + } else if (adapter instanceof CompositePDTAdapter) { final CompositePDTAdapter<?> composite = (CompositePDTAdapter<?>) adapter; + if (primitiveAdaptersByClass.containsKey(composite.targetClass())) + throw new IllegalArgumentException("Class " + composite.targetClass().getName() + + " is already registered as a primitive PDT adapter"); adaptersByName.put(composite.typeName(), composite); adaptersByClass.put(composite.targetClass(), composite); } @@ -96,6 +110,14 @@ public final class ProviderDefinedTypeRegistry { return Optional.ofNullable(adaptersByClass.get(clazz)); } + public Optional<PrimitivePDTAdapter<?>> getPrimitiveAdapterByName(final String name) { + return Optional.ofNullable(primitiveAdaptersByName.get(name)); + } + + public Optional<PrimitivePDTAdapter<?>> getPrimitiveAdapterByClass(final Class<?> clazz) { + return Optional.ofNullable(primitiveAdaptersByClass.get(clazz)); + } + /** * Attempts to hydrate a {@link ProviderDefinedType} into a typed object using a registered adapter. * Recursively hydrates nested PDT values in the fields map (including those inside Lists, Sets, @@ -136,6 +158,8 @@ public final class ProviderDefinedTypeRegistry { private Object hydrateValue(final Object value) { if (value instanceof ProviderDefinedType) return hydrate((ProviderDefinedType) value); + if (value instanceof PrimitiveProviderDefinedType) + return hydratePrimitive((PrimitiveProviderDefinedType) value); if (value instanceof List) { final List<Object> result = new ArrayList<>(); for (final Object item : (List<?>) value) @@ -157,6 +181,28 @@ public final class ProviderDefinedTypeRegistry { return value; } + /** + * Attempts to hydrate a {@link PrimitiveProviderDefinedType} into a typed object using a registered + * {@link PrimitivePDTAdapter}. Returns the original primitive PDT if no adapter is found or if the + * adapter throws an exception. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public Object hydratePrimitive(final PrimitiveProviderDefinedType pdt) { + final PrimitivePDTAdapter adapter = primitiveAdaptersByName.get(pdt.getName()); + if (adapter == null) { + logger.warn("No PrimitivePDTAdapter registered for '{}', returning raw PrimitiveProviderDefinedType", + pdt.getName()); + return pdt; + } + try { + return adapter.fromValue(pdt.getValue()); + } catch (final Exception e) { + logger.warn("Failed to hydrate PrimitiveProviderDefinedType '{}', returning raw: {}", + pdt.getName(), e.getMessage()); + return pdt; + } + } + /** * A reflective adapter synthesized from a {@link ProviderDefined}-annotated class. */ diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeRegistryTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeRegistryTest.java index 7158cb71f5..3442038337 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeRegistryTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/io/pdt/ProviderDefinedTypeRegistryTest.java @@ -413,4 +413,145 @@ public class ProviderDefinedTypeRegistryTest { assertEquals(10, ((Point) innerValue).x); assertEquals(20, ((Point) innerValue).y); } + + // === Primitive PDT Adapter tests === + + static class Uint32 { + final long value; + Uint32(long value) { this.value = value; } + } + + static class Uint32Adapter implements PrimitivePDTAdapter<Uint32> { + @Override public String typeName() { return "Uint32"; } + @Override public Class<Uint32> targetClass() { return Uint32.class; } + @Override public String toValue(Uint32 obj) { return Long.toString(obj.value); } + @Override public Uint32 fromValue(String value) { return new Uint32(Long.parseLong(value)); } + } + + static class FailingPrimitiveAdapter implements PrimitivePDTAdapter<Uint32> { + @Override public String typeName() { return "FailPrim"; } + @Override public Class<Uint32> targetClass() { return Uint32.class; } + @Override public String toValue(Uint32 obj) { return "0"; } + @Override public Uint32 fromValue(String value) { throw new RuntimeException("intentional primitive failure"); } + } + + @Test + public void shouldRegisterAndLookUpPrimitiveAdapterByName() { + final ProviderDefinedTypeRegistry registry = ProviderDefinedTypeRegistry.empty(); + registry.register(new Uint32Adapter()); + + final Optional<PrimitivePDTAdapter<?>> found = registry.getPrimitiveAdapterByName("Uint32"); + assertTrue(found.isPresent()); + assertEquals("Uint32", found.get().typeName()); + } + + @Test + public void shouldRegisterAndLookUpPrimitiveAdapterByClass() { + final ProviderDefinedTypeRegistry registry = ProviderDefinedTypeRegistry.empty(); + registry.register(new Uint32Adapter()); + + final Optional<PrimitivePDTAdapter<?>> found = registry.getPrimitiveAdapterByClass(Uint32.class); + assertTrue(found.isPresent()); + assertEquals(Uint32.class, found.get().targetClass()); + } + + @Test + public void shouldHydratePrimitive() { + final ProviderDefinedTypeRegistry registry = ProviderDefinedTypeRegistry.empty(); + registry.register(new Uint32Adapter()); + + final PrimitiveProviderDefinedType pdt = new PrimitiveProviderDefinedType("Uint32", "42"); + final Object result = registry.hydratePrimitive(pdt); + assertTrue(result instanceof Uint32); + assertEquals(42L, ((Uint32) result).value); + } + + @Test + public void shouldReturnRawPrimitivePdtWhenNoAdapterRegistered() { + final ProviderDefinedTypeRegistry registry = ProviderDefinedTypeRegistry.empty(); + + final PrimitiveProviderDefinedType pdt = new PrimitiveProviderDefinedType("Unknown", "x"); + final Object result = registry.hydratePrimitive(pdt); + assertSame(pdt, result); + } + + @Test + public void shouldReturnRawPrimitivePdtWhenAdapterThrows() { + final ProviderDefinedTypeRegistry registry = ProviderDefinedTypeRegistry.empty(); + registry.register(new FailingPrimitiveAdapter()); + + final PrimitiveProviderDefinedType pdt = new PrimitiveProviderDefinedType("FailPrim", "42"); + final Object result = registry.hydratePrimitive(pdt); + assertSame(pdt, result); + } + + @Test + public void shouldThrowOnDualRegistrationPrimitiveAfterComposite() { + final ProviderDefinedTypeRegistry registry = ProviderDefinedTypeRegistry.empty(); + registry.register(new PointAdapter()); + + // Attempt to register Point.class as a primitive (already registered as composite) + final PrimitivePDTAdapter<Point> primitivePoint = new PrimitivePDTAdapter<Point>() { + @Override public String typeName() { return "PointPrim"; } + @Override public Class<Point> targetClass() { return Point.class; } + @Override public String toValue(Point obj) { return obj.x + "," + obj.y; } + @Override public Point fromValue(String value) { return new Point(0, 0); } + }; + + try { + registry.register(primitivePoint); + fail("Expected IllegalArgumentException for dual registration"); + } catch (final IllegalArgumentException e) { + assertTrue(e.getMessage().contains("already registered as a composite")); + } + } + + @Test + public void shouldThrowOnDualRegistrationCompositeAfterPrimitive() { + final ProviderDefinedTypeRegistry registry = ProviderDefinedTypeRegistry.empty(); + registry.register(new Uint32Adapter()); + + // Attempt to register Uint32.class as a composite (already registered as primitive) + final CompositePDTAdapter<Uint32> compositeUint32 = new CompositePDTAdapter<Uint32>() { + @Override public String typeName() { return "Uint32Comp"; } + @Override public Class<Uint32> targetClass() { return Uint32.class; } + @Override public Map<String, Object> toFields(Uint32 obj) { return new HashMap<>(); } + @Override public Uint32 fromFields(Map<String, Object> fields) { return new Uint32(0); } + }; + + try { + registry.register(compositeUint32); + fail("Expected IllegalArgumentException for dual registration"); + } catch (final IllegalArgumentException e) { + assertTrue(e.getMessage().contains("already registered as a primitive")); + } + } + + @Test + public void shouldHydratePrimitiveNestedInsideComposite() { + final ProviderDefinedTypeRegistry registry = ProviderDefinedTypeRegistry.empty(); + registry.register(new Uint32Adapter()); + + // A composite type "Container" with a primitive nested field + registry.register(new CompositePDTAdapter<Map>() { + @Override public String typeName() { return "Container"; } + @Override public Class<Map> targetClass() { return Map.class; } + @Override public Map<String, Object> toFields(Map obj) { return new HashMap<>(); } + @SuppressWarnings("unchecked") + @Override public Map fromFields(Map<String, Object> fields) { return fields; } + }); + + final Map<String, Object> containerFields = new HashMap<>(); + containerFields.put("id", new PrimitiveProviderDefinedType("Uint32", "99")); + containerFields.put("label", "test"); + final ProviderDefinedType containerPdt = new ProviderDefinedType("Container", containerFields); + + final Object result = registry.hydrate(containerPdt); + assertTrue(result instanceof Map); + @SuppressWarnings("unchecked") + final Map<String, Object> resultMap = (Map<String, Object>) result; + assertTrue(resultMap.get("id") instanceof Uint32); + assertEquals(99L, ((Uint32) resultMap.get("id")).value); + assertEquals("test", resultMap.get("label")); + } } diff --git a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/GraphBinaryWriterPdtTest.java b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/GraphBinaryWriterPdtTest.java index 919d9ba118..0d57c515e5 100644 --- a/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/GraphBinaryWriterPdtTest.java +++ b/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/util/ser/binary/GraphBinaryWriterPdtTest.java @@ -24,6 +24,8 @@ import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryReader; import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryWriter; import org.apache.tinkerpop.gremlin.structure.io.binary.TypeSerializerRegistry; import org.apache.tinkerpop.gremlin.structure.io.pdt.CompositePDTAdapter; +import org.apache.tinkerpop.gremlin.structure.io.pdt.PrimitivePDTAdapter; +import org.apache.tinkerpop.gremlin.structure.io.pdt.PrimitiveProviderDefinedType; import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefined; import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedType; import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeRegistry; @@ -133,4 +135,77 @@ public class GraphBinaryWriterPdtTest { final ProviderDefinedType result = reader.read(buffer); assertEquals(pdt, result); } + + // === Primitive PDT Adapter write-path tests === + + static class Uint32 { + final long value; + Uint32(long value) { this.value = value; } + } + + static class Uint32Adapter implements PrimitivePDTAdapter<Uint32> { + @Override public String typeName() { return "Uint32"; } + @Override public Class<Uint32> targetClass() { return Uint32.class; } + @Override public String toValue(Uint32 obj) { return Long.toString(obj.value); } + @Override public Uint32 fromValue(String value) { return new Uint32(Long.parseLong(value)); } + } + + @Test + public void shouldDehydratePrimitiveAdapterOnWritePathAndHydrateBack() throws IOException { + final ProviderDefinedTypeRegistry pdtRegistry = ProviderDefinedTypeRegistry.empty(); + pdtRegistry.register(new Uint32Adapter()); + + final GraphBinaryWriter registryWriter = new GraphBinaryWriter(TypeSerializerRegistry.INSTANCE, pdtRegistry); + final GraphBinaryReader registryReader = new GraphBinaryReader(TypeSerializerRegistry.INSTANCE, pdtRegistry); + + final Uint32 original = new Uint32(12345L); + + final Buffer buffer = bufferFactory.create(allocator.buffer()); + registryWriter.write(original, buffer); + buffer.readerIndex(0); + + final Uint32 result = registryReader.read(buffer); + assertEquals(12345L, result.value); + } + + @Test + public void shouldRoundTripPrimitiveProviderDefinedTypeWithoutRegistry() throws IOException { + final PrimitiveProviderDefinedType pdt = new PrimitiveProviderDefinedType("Uint32", "99"); + + final Buffer buffer = bufferFactory.create(allocator.buffer()); + writer.write(pdt, buffer); + buffer.readerIndex(0); + + final PrimitiveProviderDefinedType result = reader.read(buffer); + assertEquals(pdt, result); + } + + @Test + public void shouldRoundTripPrimitiveNestedInComposite() throws IOException { + final ProviderDefinedTypeRegistry pdtRegistry = ProviderDefinedTypeRegistry.empty(); + pdtRegistry.register(new Uint32Adapter()); + pdtRegistry.register(new UnannotatedTypeAdapter()); + + final GraphBinaryWriter registryWriter = new GraphBinaryWriter(TypeSerializerRegistry.INSTANCE, pdtRegistry); + final GraphBinaryReader registryReader = new GraphBinaryReader(TypeSerializerRegistry.INSTANCE, pdtRegistry); + + // Build a composite PDT with a nested primitive value + final Map<String, Object> fields = new LinkedHashMap<>(); + fields.put("value", 7); + fields.put("id", new PrimitiveProviderDefinedType("Uint32", "42")); + final ProviderDefinedType compositePdt = new ProviderDefinedType("UnannotatedType", fields); + + final Buffer buffer = bufferFactory.create(allocator.buffer()); + registryWriter.write(compositePdt, buffer); + buffer.readerIndex(0); + + // The reader hydrates the composite (via UnannotatedTypeAdapter) and the nested primitive + // should have been hydrated to Uint32 by the registry's hydrateValue recursion + final Object result = registryReader.read(buffer); + assertTrue(result instanceof UnannotatedType); + // Note: UnannotatedTypeAdapter only maps "value" field to an int, so the hydrated "id" field + // ends up being handled during the composite adapter's fromFields. Since the adapter + // only reads "value", we verify the composite round-tripped correctly. + assertEquals(7, ((UnannotatedType) result).value); + } }
