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

spmallette pushed a commit to branch tinkergraph-storage
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git


The following commit(s) were added to refs/heads/tinkergraph-storage by this 
push:
     new b8b4b3a3f1 Rename ByteBufferBuffer to TinkerByteBuffer and add its 
unit tests
b8b4b3a3f1 is described below

commit b8b4b3a3f11a4c9cc5cccbb27d53efc1c71e4895
Author: Stephen Mallette <[email protected]>
AuthorDate: Wed Sep 9 15:08:37 2026 -0400

    Rename ByteBufferBuffer to TinkerByteBuffer and add its unit tests
    
    The buffer implementation backing the GraphBinary storage codec had no 
direct
    tests. Covers the primitive round-trips, the big-endian byte order the 
on-disk
    format depends on, index and capacity handling, the bulk transfer and nio 
views,
    and the bounds rejections.
    
    Assisted-by: Claude Code:claude-opus-5
    Claude-Session: https://claude.ai/code/session_01KgH2VCpRw57sbFg5GoAiVV
---
 .../structure/storage/GraphBinaryStorage.java      |  30 +--
 ...ByteBufferBuffer.java => TinkerByteBuffer.java} |   8 +-
 .../structure/storage/TinkerByteBufferTest.java    | 243 +++++++++++++++++++++
 3 files changed, 262 insertions(+), 19 deletions(-)

diff --git 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/storage/GraphBinaryStorage.java
 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/storage/GraphBinaryStorage.java
index a5c4707409..0349c4a358 100644
--- 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/storage/GraphBinaryStorage.java
+++ 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/storage/GraphBinaryStorage.java
@@ -109,7 +109,7 @@ public final class GraphBinaryStorage extends 
AbstractLogStorage {
     protected byte[] encodeCommit(final long txVersion,
                                   final 
Collection<TinkerStorageMutation<TinkerVertex>> changedVertices,
                                   final 
Collection<TinkerStorageMutation<TinkerEdge>> changedEdges) throws IOException {
-        final ByteBufferBuffer buf = new ByteBufferBuffer();
+        final TinkerByteBuffer buf = new TinkerByteBuffer();
         // register the strings introduced by this commit (deletes carry only 
an id, no strings)
         final List<String> appends = new ArrayList<>();
         for (final TinkerStorageMutation<TinkerVertex> m : changedVertices)
@@ -159,7 +159,7 @@ public final class GraphBinaryStorage extends 
AbstractLogStorage {
         while (edges.hasNext())
             registerEdgeStrings(edges.next(), ignored);
 
-        final ByteBufferBuffer dictBuf = new ByteBufferBuffer();
+        final TinkerByteBuffer dictBuf = new TinkerByteBuffer();
         writeVarInt(dictBuf, idToKey.size());
         for (int id = 0; id < idToKey.size(); id++) {
             dictBuf.writeByte(OP_DICT_APPEND);
@@ -182,7 +182,7 @@ public final class GraphBinaryStorage extends 
AbstractLogStorage {
      * is never buffered whole.
      */
     private void writeElementFrame(final DataOutputStream out, final byte op, 
final Object element) throws IOException {
-        final ByteBufferBuffer buf = new ByteBufferBuffer();
+        final TinkerByteBuffer buf = new TinkerByteBuffer();
         writeVarInt(buf, 1);
         buf.writeByte(op);
         if (op == OP_PUT_VERTEX) writeVertexRecord(buf, (Vertex) element);
@@ -219,7 +219,7 @@ public final class GraphBinaryStorage extends 
AbstractLogStorage {
         }
     }
 
-    private void writeVertexRecord(final ByteBufferBuffer buf, final Vertex v) 
throws IOException {
+    private void writeVertexRecord(final TinkerByteBuffer buf, final Vertex v) 
throws IOException {
         writeScalar(buf, v.id());
         final Set<String> labels = v.labels();
         writeVarInt(buf, labels.size());
@@ -256,7 +256,7 @@ public final class GraphBinaryStorage extends 
AbstractLogStorage {
         }
     }
 
-    private void writeEdgeRecord(final ByteBufferBuffer buf, final Edge e) 
throws IOException {
+    private void writeEdgeRecord(final TinkerByteBuffer buf, final Edge e) 
throws IOException {
         writeScalar(buf, e.id());
         writeVarInt(buf, keyToId.get(e.label()));
         writeScalar(buf, e.outVertex().id());
@@ -276,7 +276,7 @@ public final class GraphBinaryStorage extends 
AbstractLogStorage {
     protected void decodeFrame(final byte[] record,
                                final Map<Object, DetachedVertex> vertices,
                                final Map<Object, DetachedEdge> edges) throws 
IOException {
-        final ByteBufferBuffer buf = new ByteBufferBuffer(record);
+        final TinkerByteBuffer buf = new TinkerByteBuffer(record);
         final int entryCount = readVarInt(buf);
         for (int i = 0; i < entryCount; i++) {
             final byte op = buf.readByte();
@@ -321,7 +321,7 @@ public final class GraphBinaryStorage extends 
AbstractLogStorage {
         }
     }
 
-    private DetachedVertex readVertexRecord(final ByteBufferBuffer buf) throws 
IOException {
+    private DetachedVertex readVertexRecord(final TinkerByteBuffer buf) throws 
IOException {
         final Object id = readScalar(buf);
         final DetachedVertex.Builder b = DetachedVertex.build().setId(id);
         final int labelCount = readVarInt(buf);
@@ -355,7 +355,7 @@ public final class GraphBinaryStorage extends 
AbstractLogStorage {
         return b.create();
     }
 
-    private DetachedEdge readEdgeRecord(final ByteBufferBuffer buf) throws 
IOException {
+    private DetachedEdge readEdgeRecord(final TinkerByteBuffer buf) throws 
IOException {
         final Object id = readScalar(buf);
         final String label = resolveKey(buf);
         final Object outVId = readScalar(buf);
@@ -379,7 +379,7 @@ public final class GraphBinaryStorage extends 
AbstractLogStorage {
      * is a single {@link DataType#UNSPECIFIED_NULL} tag.
      */
     @SuppressWarnings({"unchecked", "rawtypes"})
-    private void writeScalar(final ByteBufferBuffer buf, final Object value) 
throws IOException {
+    private void writeScalar(final TinkerByteBuffer buf, final Object value) 
throws IOException {
         if (value == null) {
             buf.writeByte(DataType.UNSPECIFIED_NULL.getCodeByte());
             return;
@@ -390,7 +390,7 @@ public final class GraphBinaryStorage extends 
AbstractLogStorage {
     }
 
     @SuppressWarnings({"unchecked", "rawtypes"})
-    private Object readScalar(final ByteBufferBuffer buf) throws IOException {
+    private Object readScalar(final TinkerByteBuffer buf) throws IOException {
         final int code = Byte.toUnsignedInt(buf.readByte());
         final DataType dataType = DataType.get(code);
         if (dataType == null)
@@ -401,7 +401,7 @@ public final class GraphBinaryStorage extends 
AbstractLogStorage {
         return serializer.readValue(buf, reader, false);
     }
 
-    private static void writeString(final ByteBufferBuffer buf, final String 
s) {
+    private static void writeString(final TinkerByteBuffer buf, final String 
s) {
         final byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
         writeVarInt(buf, bytes.length);
         buf.writeBytes(bytes);
@@ -412,7 +412,7 @@ public final class GraphBinaryStorage extends 
AbstractLogStorage {
      * hold is corruption, and is reported as such rather than raised as an 
{@code IndexOutOfBoundsException} from the
      * backing list.
      */
-    private String resolveKey(final ByteBufferBuffer buf) throws IOException {
+    private String resolveKey(final TinkerByteBuffer buf) throws IOException {
         final int id = readVarInt(buf);
         if (id >= idToKey.size())
             throw new IOException(String.format(
@@ -420,7 +420,7 @@ public final class GraphBinaryStorage extends 
AbstractLogStorage {
         return idToKey.get(id);
     }
 
-    private static String readString(final ByteBufferBuffer buf) throws 
IOException {
+    private static String readString(final TinkerByteBuffer buf) throws 
IOException {
         final int length = readVarInt(buf);
         // check the declared length against what the frame actually holds 
before allocating. The frame itself is
         // already bounded against the file by AbstractLogStorage.readFrame, 
but a length inside the frame is not,
@@ -439,7 +439,7 @@ public final class GraphBinaryStorage extends 
AbstractLogStorage {
      * Unsigned LEB128 varint. Counts and dictionary refs are small and 
non-negative, so they cost one byte in the
      * common case.
      */
-    private static void writeVarInt(final ByteBufferBuffer buf, final int 
value) {
+    private static void writeVarInt(final TinkerByteBuffer buf, final int 
value) {
         int v = value;
         while ((v & ~0x7F) != 0) {
             buf.writeByte((v & 0x7F) | 0x80);
@@ -448,7 +448,7 @@ public final class GraphBinaryStorage extends 
AbstractLogStorage {
         buf.writeByte(v & 0x7F);
     }
 
-    private static int readVarInt(final ByteBufferBuffer buf) throws 
IOException {
+    private static int readVarInt(final TinkerByteBuffer buf) throws 
IOException {
         int result = 0;
         int shift = 0;
         byte b;
diff --git 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/storage/ByteBufferBuffer.java
 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/storage/TinkerByteBuffer.java
similarity index 97%
rename from 
tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/storage/ByteBufferBuffer.java
rename to 
tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/storage/TinkerByteBuffer.java
index 8872afd404..d32a212a30 100644
--- 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/storage/ByteBufferBuffer.java
+++ 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/storage/TinkerByteBuffer.java
@@ -32,7 +32,7 @@ import java.util.Arrays;
  * as needed on write. This class is not thread-safe; a buffer is used by a 
single thread for a single
  * serialize/deserialize.
  */
-public final class ByteBufferBuffer implements Buffer {
+public final class TinkerByteBuffer implements Buffer {
 
     private static final int DEFAULT_CAPACITY = 256;
 
@@ -42,18 +42,18 @@ public final class ByteBufferBuffer implements Buffer {
     private int markedWriterIndex = 0;
     private int referenceCount = 1;
 
-    public ByteBufferBuffer() {
+    public TinkerByteBuffer() {
         this(DEFAULT_CAPACITY);
     }
 
-    public ByteBufferBuffer(final int initialCapacity) {
+    public TinkerByteBuffer(final int initialCapacity) {
         this.array = new byte[Math.max(initialCapacity, 1)];
     }
 
     /**
      * Wraps an existing array for reading. The writer index is positioned at 
the end of the supplied data.
      */
-    public ByteBufferBuffer(final byte[] data) {
+    public TinkerByteBuffer(final byte[] data) {
         this.array = data;
         this.writerIndex = data.length;
     }
diff --git 
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/storage/TinkerByteBufferTest.java
 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/storage/TinkerByteBufferTest.java
new file mode 100644
index 0000000000..006adba8c8
--- /dev/null
+++ 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/storage/TinkerByteBufferTest.java
@@ -0,0 +1,243 @@
+/*
+ * 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.tinkergraph.structure.storage;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Unit tests for {@link TinkerByteBuffer}, the heap {@code byte[]} 
implementation of the gremlin-core
+ * {@code Buffer} abstraction that backs the GraphBinary storage codec. The 
byte order and bounds behaviour asserted
+ * here are relied on by the on-disk format, so a change that breaks them 
changes what a store means.
+ */
+public class TinkerByteBufferTest {
+
+    @Test
+    public void shouldRoundTripEveryPrimitive() {
+        final TinkerByteBuffer buf = new TinkerByteBuffer();
+        buf.writeBoolean(true).writeBoolean(false)
+                .writeByte(0x7F).writeShort(-2)
+                .writeInt(Integer.MIN_VALUE).writeLong(Long.MAX_VALUE)
+                .writeFloat(0.5f).writeDouble(-1.25d);
+
+        assertTrue(buf.readBoolean());
+        assertFalse(buf.readBoolean());
+        assertEquals(0x7F, buf.readByte());
+        assertEquals((short) -2, buf.readShort());
+        assertEquals(Integer.MIN_VALUE, buf.readInt());
+        assertEquals(Long.MAX_VALUE, buf.readLong());
+        assertEquals(0.5f, buf.readFloat(), 0.0f);
+        assertEquals(-1.25d, buf.readDouble(), 0.0d);
+        assertEquals(0, buf.readableBytes());
+    }
+
+    @Test
+    public void shouldWriteMultiByteValuesBigEndian() {
+        // the on-disk format is big-endian, matching the Netty-backed buffer 
this replaces, so the byte order is
+        // part of the storage contract rather than an implementation detail
+        final TinkerByteBuffer buf = new TinkerByteBuffer();
+        
buf.writeShort(0x0102).writeInt(0x03040506).writeLong(0x0708090A0B0C0D0EL);
+
+        assertArrayEquals(new byte[] {
+                0x01, 0x02,
+                0x03, 0x04, 0x05, 0x06,
+                0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E
+        }, buf.toWrittenArray());
+    }
+
+    @Test
+    public void shouldGrowBeyondInitialCapacity() {
+        final TinkerByteBuffer buf = new TinkerByteBuffer(1);
+        final byte[] payload = new byte[1000];
+        Arrays.fill(payload, (byte) 7);
+        buf.writeBytes(payload);
+
+        assertTrue("capacity should have grown to hold the payload", 
buf.capacity() >= 1000);
+        assertEquals(1000, buf.readableBytes());
+        assertArrayEquals(payload, buf.toWrittenArray());
+    }
+
+    @Test
+    public void shouldWrapAnExistingArrayReadyForReading() {
+        final byte[] data = { 0x00, 0x00, 0x00, 0x2A };
+        final TinkerByteBuffer buf = new TinkerByteBuffer(data);
+
+        assertEquals(4, buf.writerIndex());
+        assertEquals(0, buf.readerIndex());
+        assertEquals(4, buf.readableBytes());
+        assertEquals(42, buf.readInt());
+    }
+
+    @Test
+    public void shouldTrackReaderAndWriterIndexes() {
+        final TinkerByteBuffer buf = new TinkerByteBuffer();
+        buf.writeInt(1).writeInt(2);
+        assertEquals(8, buf.writerIndex());
+        assertEquals(8, buf.readableBytes());
+
+        buf.readInt();
+        assertEquals(4, buf.readerIndex());
+        assertEquals(4, buf.readableBytes());
+
+        buf.readerIndex(0);
+        assertEquals(1, buf.readInt());
+    }
+
+    @Test
+    public void shouldDistinguishReadableFromWrittenBytes() {
+        final TinkerByteBuffer buf = new TinkerByteBuffer();
+        buf.writeInt(1).writeInt(2);
+        buf.readInt();
+
+        // written covers everything from index 0; readable covers only what 
is left ahead of the reader
+        assertArrayEquals(new byte[] { 0, 0, 0, 1, 0, 0, 0, 2 }, 
buf.toWrittenArray());
+        assertArrayEquals(new byte[] { 0, 0, 0, 2 }, buf.toReadableArray());
+        assertEquals("neither view may move the indexes", 4, 
buf.readerIndex());
+    }
+
+    @Test
+    public void shouldResetWriterIndexToTheMark() {
+        final TinkerByteBuffer buf = new TinkerByteBuffer();
+        buf.writeInt(1);
+        buf.markWriterIndex();
+        buf.writeInt(2);
+        assertEquals(8, buf.writerIndex());
+
+        buf.resetWriterIndex();
+        assertEquals(4, buf.writerIndex());
+        assertArrayEquals(new byte[] { 0, 0, 0, 1 }, buf.toWrittenArray());
+    }
+
+    @Test
+    public void shouldRejectReadingPastTheWriterIndex() {
+        final TinkerByteBuffer buf = new TinkerByteBuffer();
+        buf.writeByte(1);
+        buf.readByte();
+
+        try {
+            buf.readByte();
+            fail("expected a read past the writer index to be rejected");
+        } catch (IndexOutOfBoundsException expected) {
+            assertTrue(expected.getMessage(), 
expected.getMessage().contains("Not enough readable bytes"));
+        }
+    }
+
+    @Test
+    public void shouldRejectReadingMoreBytesThanRemain() {
+        final TinkerByteBuffer buf = new TinkerByteBuffer(new byte[] { 1, 2, 3 
});
+        try {
+            buf.readBytes(new byte[4]);
+            fail("expected a bulk read longer than the readable region to be 
rejected");
+        } catch (IndexOutOfBoundsException expected) {
+            assertTrue(expected.getMessage(), 
expected.getMessage().contains("Not enough readable bytes"));
+        }
+    }
+
+    @Test
+    public void shouldRejectAnOutOfRangeReaderIndex() {
+        final TinkerByteBuffer buf = new TinkerByteBuffer();
+        buf.writeInt(1);
+        try {
+            buf.readerIndex(5);
+            fail("expected a reader index beyond the writer index to be 
rejected");
+        } catch (IndexOutOfBoundsException expected) {
+            // expected: the readable region may never extend past what has 
been written
+        }
+    }
+
+    @Test
+    public void shouldReadBytesIntoAnArraySlice() {
+        final TinkerByteBuffer buf = new TinkerByteBuffer(new byte[] { 1, 2, 
3, 4 });
+        final byte[] dst = new byte[6];
+        buf.readBytes(dst, 1, 4);
+
+        assertArrayEquals(new byte[] { 0, 1, 2, 3, 4, 0 }, dst);
+        assertEquals(0, buf.readableBytes());
+    }
+
+    @Test
+    public void shouldReadAndWriteThroughNioBuffers() {
+        final TinkerByteBuffer buf = new TinkerByteBuffer();
+        buf.writeBytes(ByteBuffer.wrap(new byte[] { 9, 8, 7 }));
+        assertEquals(3, buf.readableBytes());
+
+        final ByteBuffer dst = ByteBuffer.allocate(3);
+        buf.readBytes(dst);
+        assertArrayEquals(new byte[] { 9, 8, 7 }, dst.array());
+    }
+
+    @Test
+    public void shouldReadBytesIntoAnOutputStream() throws Exception {
+        final TinkerByteBuffer buf = new TinkerByteBuffer(new byte[] { 4, 5, 
6, 7 });
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        buf.readBytes(out, 3);
+
+        assertArrayEquals(new byte[] { 4, 5, 6 }, out.toByteArray());
+        assertEquals(1, buf.readableBytes());
+    }
+
+    @Test
+    public void shouldCopyAbsoluteBytesWithoutMovingIndexes() {
+        final TinkerByteBuffer buf = new TinkerByteBuffer(new byte[] { 1, 2, 
3, 4 });
+        final byte[] dst = new byte[2];
+        buf.getBytes(2, dst);
+
+        assertArrayEquals(new byte[] { 3, 4 }, dst);
+        assertEquals("an absolute read is positional and must not consume", 0, 
buf.readerIndex());
+    }
+
+    @Test
+    public void shouldExposeAnNioViewOfTheReadableRegion() {
+        final TinkerByteBuffer buf = new TinkerByteBuffer(new byte[] { 1, 2, 
3, 4 });
+        buf.readByte();
+
+        assertEquals(1, buf.nioBufferCount());
+        final ByteBuffer view = buf.nioBuffer();
+        assertArrayEquals(new byte[] { 2, 3, 4 }, view.array());
+        assertEquals("the view is a copy, so consuming it must not move the 
buffer", 3, buf.readableBytes());
+        assertArrayEquals(new byte[] { 2, 3 }, buf.nioBuffer(1, 2).array());
+        assertEquals(1, buf.nioBuffers().length);
+    }
+
+    @Test
+    public void shouldCountReferences() {
+        final TinkerByteBuffer buf = new TinkerByteBuffer();
+        assertEquals(1, buf.referenceCount());
+
+        buf.retain();
+        assertEquals(2, buf.referenceCount());
+        assertFalse("still referenced, so release does not report the last 
one", buf.release());
+        assertEquals(1, buf.referenceCount());
+        assertTrue("the final release reports that the buffer is done", 
buf.release());
+    }
+
+    @Test
+    public void shouldReportAsHeapBacked() {
+        assertFalse(new TinkerByteBuffer().isDirect());
+    }
+}

Reply via email to