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

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


The following commit(s) were added to refs/heads/TINKERPOP-1942 by this push:
     new 0551ade  Add serialization/write support
0551ade is described below

commit 0551adebe09fcecd67a39d0620902f5c09578128
Author: Jorge Bay Gondra <jorgebaygon...@gmail.com>
AuthorDate: Mon Nov 19 15:11:05 2018 +0100

    Add serialization/write support
---
 docs/src/dev/io/graphbinary.asciidoc               | 22 +++++-
 .../gremlin/driver/ser/GraphBinarySerializer.java  | 39 +++++-----
 .../gremlin/driver/ser/binary/DataType.java        |  3 +-
 .../driver/ser/binary/GraphBinaryReader.java       | 10 +--
 .../driver/ser/binary/GraphBinaryWriter.java       | 91 ++++++++++++++++++++++
 .../gremlin/driver/ser/binary/TypeSerializer.java  | 17 +++-
 .../driver/ser/binary/TypeSerializerRegistry.java  | 21 +++--
 .../ser/binary/types/ByteCodeSerializer.java       | 56 ++++++++++++-
 .../driver/ser/binary/types/MapSerializer.java     | 37 +++++++--
 .../ser/binary/types/RequestMessageSerializer.java | 29 +++++--
 .../ser/binary/types/SimpleTypeSerializer.java     | 63 ++++++++++++++-
 .../ser/binary/types/SingleTypeSerializer.java     | 49 +++++++++---
 .../driver/ser/binary/types/StringSerializer.java  | 15 +++-
 .../driver/ser/binary/types/UUIDSerializer.java    | 15 ++++
 .../ser/GraphBinaryReaderWriterRoundTripTest.java  | 65 ++++++++++++++++
 15 files changed, 465 insertions(+), 67 deletions(-)

diff --git a/docs/src/dev/io/graphbinary.asciidoc 
b/docs/src/dev/io/graphbinary.asciidoc
index b3ef08c..a841a0a 100644
--- a/docs/src/dev/io/graphbinary.asciidoc
+++ b/docs/src/dev/io/graphbinary.asciidoc
@@ -135,6 +135,7 @@ The total length is not part of the message as the 
transport layer will provide
 - `0x24`: Byte
 - `0x25`: ByteBuffer
 - `0x26`: Short
+- `0xff`: Unspecified null object
 - `0x00`: Custom
 
 ==== Extended Types
@@ -155,6 +156,19 @@ The total length is not part of the message as the 
transport layer will provide
 - `0x8d`: ZonedDateTime
 - `0x8e`: ZoneOffset
 
+=== Null handling
+
+The serialization format defines two ways to represent null values:
+
+- Unspecified null object
+- Fully-qualified null
+
+When a parent type can contain any subtype e.g., a object collection, a `null` 
value must be represented using the
+"Unspecified Null Object" type code and the null value flag.
+
+In contrast, when the parent type contains a type parameter that must be 
specified, a `null` value is represented using
+a fully-qualified object using the appropriate type code and type information.
+
 === Data Type Formats
 
 ==== Int
@@ -485,6 +499,12 @@ Where:
 
 - `{blob}` is a `ByteBuffer`.
 
+==== Unspecified Null Object
+
+A `null` value for an unspecified Object value.
+
+It's represented using the null `{value_flag}` set and no sequence of bytes.
+
 ==== Char
 
 Format: one to four bytes representing a single UTF8 char, according to the 
Unicode standard.
@@ -516,7 +536,7 @@ Where:
 
 ==== InetAddress
 
-Format: Same as `ByteBuffer`.
+Format: Same as `ByteBuffer`, having only 4 byte or 16 byte sequences allowed.
 
 ==== Instant
 
diff --git 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/GraphBinarySerializer.java
 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/GraphBinarySerializer.java
index 7d4b768..12d744a 100644
--- 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/GraphBinarySerializer.java
+++ 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/GraphBinarySerializer.java
@@ -24,29 +24,30 @@ import 
org.apache.tinkerpop.gremlin.driver.message.RequestMessage;
 import org.apache.tinkerpop.gremlin.driver.message.ResponseMessage;
 
 public class GraphBinarySerializer extends AbstractMessageSerializer {
-  @Override
-  public ByteBuf serializeResponseAsBinary(ResponseMessage responseMessage, 
ByteBufAllocator allocator) throws SerializationException {
-    return null;
-  }
+    @Override
+    public ByteBuf serializeResponseAsBinary(ResponseMessage responseMessage, 
ByteBufAllocator allocator) throws SerializationException {
+        return null;
+    }
 
-  @Override
-  public ByteBuf serializeRequestAsBinary(RequestMessage requestMessage, 
ByteBufAllocator allocator) throws SerializationException {
-    return null;
-  }
+    @Override
+    public ByteBuf serializeRequestAsBinary(RequestMessage requestMessage, 
ByteBufAllocator allocator) throws SerializationException {
 
-  @Override
-  public RequestMessage deserializeRequest(ByteBuf msg) throws 
SerializationException {
-    //TODO: Use BinaryReader
-    return null;
-  }
+        return null;
+    }
 
-  @Override
-  public ResponseMessage deserializeResponse(ByteBuf msg) throws 
SerializationException {
-    return null;
-  }
+    @Override
+    public RequestMessage deserializeRequest(ByteBuf msg) throws 
SerializationException {
+        //TODO: Use BinaryReader
+        return null;
+    }
+
+    @Override
+    public ResponseMessage deserializeResponse(ByteBuf msg) throws 
SerializationException {
+        return null;
+    }
 
-  @Override
-  public String[] mimeTypesSupported() {
+    @Override
+    public String[] mimeTypesSupported() {
     return new String[0];
   }
 }
diff --git 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/DataType.java
 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/DataType.java
index acd1a80..a9e6065 100644
--- 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/DataType.java
+++ 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/DataType.java
@@ -42,7 +42,8 @@ public enum DataType {
     VERTEXPROPERTY(0X12),
     BARRIER(0X13),
     BINDING(0X14),
-    BYTECODE(0X15);
+    BYTECODE(0X15),
+    UNSPECIFIED_NULL(0xFF);
 
     private final int code;
     private static final Map<Integer, DataType> typeByCode = new HashMap<>();
diff --git 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/GraphBinaryReader.java
 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/GraphBinaryReader.java
index cc3fcdb..0915d65 100644
--- 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/GraphBinaryReader.java
+++ 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/GraphBinaryReader.java
@@ -30,9 +30,8 @@ public class GraphBinaryReader {
 
     /**
      * Reads a value for an specific type.
-     * @throws SerializationException
      */
-    public <T> T readValue(ByteBuf buffer, Class<T> type) throws 
SerializationException {
+    public <T> T readValue(ByteBuf buffer, Class<T> type, boolean nullable) 
throws SerializationException {
         if(buffer == null) {
             throw new IllegalArgumentException("input cannot be null.");
         } else if(type == null) {
@@ -40,14 +39,13 @@ public class GraphBinaryReader {
         }
 
         TypeSerializer<T> serializer = registry.getSerializer(type);
-        return serializer.readValue(buffer, this);
+        return serializer.readValue(buffer, this, nullable);
     }
 
     /**
-     * Reads the type information and value of a given buffer from fully 
qualified format.
-     * @throws SerializationException
+     * Reads the type code, information and value of a given buffer with 
fully-qualified format.
      */
-    public <T> T readFullyQualifiedObject(ByteBuf buffer) throws 
SerializationException {
+    public <T> T read(ByteBuf buffer) throws SerializationException {
         // Fully-qualified format: {type_code}{type_info}{value_flag}{value}
         DataType type = DataType.get(buffer.readByte());
         TypeSerializer<T> serializer = registry.getSerializer(type);
diff --git 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/GraphBinaryWriter.java
 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/GraphBinaryWriter.java
new file mode 100644
index 0000000..78c09b3
--- /dev/null
+++ 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/GraphBinaryWriter.java
@@ -0,0 +1,91 @@
+/*
+ * 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.driver.ser.binary;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.Unpooled;
+import org.apache.tinkerpop.gremlin.driver.ser.SerializationException;
+
+public class GraphBinaryWriter {
+    private final TypeSerializerRegistry registry;
+    private final static byte[] valueFlagNull = new byte[] { 0x01 };
+    private final static byte[] valueFlagNone = new byte[] { 0 };
+    private final static byte[] unspecifiedNull = new byte[] { 
DataType.UNSPECIFIED_NULL.getCodeByte(), 0x01};
+
+    public GraphBinaryWriter() {
+        registry = TypeSerializerRegistry.INSTANCE;
+    }
+
+    /**
+     * Writes a value without including type information.
+     */
+    public <T> ByteBuf writeValue(T value, ByteBufAllocator allocator, boolean 
nullable) throws SerializationException {
+        if (value == null) {
+            if (!nullable) {
+                throw new SerializationException("Unexpected null value when 
nullable is false");
+            }
+
+            return getValueFlagNull();
+        }
+
+        Class<?> objectClass = value.getClass();
+
+        TypeSerializer<T> serializer = (TypeSerializer<T>) 
registry.getSerializer(objectClass);
+        return serializer.writeValue(value, allocator, this, nullable);
+    }
+
+    /**
+     * Writes an object in fully-qualified format, containing 
{type_code}{type_info}{value_flag}{value}.
+     */
+    public <T> ByteBuf write(T value, ByteBufAllocator allocator) throws 
SerializationException {
+        if (value == null) {
+            // return Object of type "unspecified object null" with the value 
flag set to null.
+            return Unpooled.wrappedBuffer(unspecifiedNull);
+        }
+
+        Class<?> objectClass = value.getClass();
+
+        TypeSerializer<T> serializer = (TypeSerializer<T>) 
registry.getSerializer(objectClass);
+        return serializer.write(value, allocator, this);
+    }
+
+    /**
+     * Represents a null value of a specific type, useful when the parent type 
contains a type parameter that must be
+     * specified.
+     */
+    public <T> ByteBuf writeFullyQualifiedNull(Class<T> objectClass, 
ByteBufAllocator allocator) throws SerializationException {
+        TypeSerializer<T> serializer = registry.getSerializer(objectClass);
+        return serializer.write(null, allocator, this);
+    }
+
+    /**
+     * Gets a buffer containing a single byte representing the null value_flag.
+     */
+    public ByteBuf getValueFlagNull() {
+        return Unpooled.wrappedBuffer(valueFlagNull);
+    }
+
+    /**
+     * Gets a buffer containing a single byte with value 0, representing an 
unset value_flag.
+     */
+    public ByteBuf getValueFlagNone() {
+        return Unpooled.wrappedBuffer(valueFlagNone);
+    }
+}
diff --git 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/TypeSerializer.java
 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/TypeSerializer.java
index afeb701..2591a25 100644
--- 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/TypeSerializer.java
+++ 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/TypeSerializer.java
@@ -19,6 +19,7 @@
 package org.apache.tinkerpop.gremlin.driver.ser.binary;
 
 import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
 import org.apache.tinkerpop.gremlin.driver.ser.SerializationException;
 
 public interface TypeSerializer<T> {
@@ -29,8 +30,20 @@ public interface TypeSerializer<T> {
 
     /**
      * Reads the value from the buffer (not the type information) and returns 
an instance of T.
-     * <p>Implementors should throw an exception when a complex type doesn't 
support </p>
+     * <p>
+     *     Implementors should throw an exception when a complex type doesn't 
support reading without the type
+     *     information
+     * </p>
      */
-    T readValue(ByteBuf buffer, GraphBinaryReader context) throws 
SerializationException;
+    T readValue(ByteBuf buffer, GraphBinaryReader context, boolean nullable) 
throws SerializationException;
 
+    /**
+     * Writes the type code, information and value to buffer.
+     */
+    ByteBuf write(T value, ByteBufAllocator allocator, GraphBinaryWriter 
context) throws SerializationException;
+
+    /**
+     * Writes the value to buffer, composed by the value flag and the sequence 
of bytes.
+     */
+    ByteBuf writeValue(T value, ByteBufAllocator allocator, GraphBinaryWriter 
context, boolean nullable)throws SerializationException;
 }
diff --git 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/TypeSerializerRegistry.java
 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/TypeSerializerRegistry.java
index ef59197..76b03ec 100644
--- 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/TypeSerializerRegistry.java
+++ 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/TypeSerializerRegistry.java
@@ -19,6 +19,7 @@
 package org.apache.tinkerpop.gremlin.driver.ser.binary;
 
 import org.apache.tinkerpop.gremlin.driver.message.RequestMessage;
+import org.apache.tinkerpop.gremlin.driver.ser.SerializationException;
 import org.apache.tinkerpop.gremlin.driver.ser.binary.types.*;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 
@@ -38,7 +39,7 @@ public class TypeSerializerRegistry {
 
         put(String.class, DataType.STRING, new StringSerializer());
         put(UUID.class, DataType.UUID, new UUIDSerializer());
-        put(Map.class, DataType.MAP, new MapSerializer());
+        put(HashMap.class, DataType.MAP, new MapSerializer());
 
         put(Integer.class, DataType.INT, SingleTypeSerializer.IntSerializer);
         put(Long.class, DataType.LONG, SingleTypeSerializer.LongSerializer);
@@ -58,13 +59,19 @@ public class TypeSerializerRegistry {
         return this;
     }
 
-    public <T> TypeSerializer<T> getSerializer(Class<T> type) {
-        //TODO: Check null
-        return (TypeSerializer<T>) serializers.get(type);
+    public <T> TypeSerializer<T> getSerializer(Class<T> type) throws 
SerializationException {
+        return validateInstance(serializers.get(type), type.getTypeName());
     }
 
-    public <T> TypeSerializer<T> getSerializer(DataType dataType) {
-        //TODO: Check null
-        return (TypeSerializer<T>) serializersByDataType.get(dataType);
+    public <T> TypeSerializer<T> getSerializer(DataType dataType) throws 
SerializationException {
+        return validateInstance(serializersByDataType.get(dataType), 
dataType.toString());
+    }
+
+    private static TypeSerializer validateInstance(TypeSerializer serializer, 
String typeName) throws SerializationException {
+        if (serializer == null) {
+            throw new SerializationException(String.format("Serializer for 
type %s not found", typeName));
+        }
+
+        return serializer;
     }
 }
diff --git 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/ByteCodeSerializer.java
 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/ByteCodeSerializer.java
index 301edcd..9cf3e10 100644
--- 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/ByteCodeSerializer.java
+++ 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/ByteCodeSerializer.java
@@ -19,10 +19,16 @@
 package org.apache.tinkerpop.gremlin.driver.ser.binary.types;
 
 import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.CompositeByteBuf;
 import org.apache.tinkerpop.gremlin.driver.ser.SerializationException;
+import org.apache.tinkerpop.gremlin.driver.ser.binary.DataType;
 import org.apache.tinkerpop.gremlin.driver.ser.binary.GraphBinaryReader;
+import org.apache.tinkerpop.gremlin.driver.ser.binary.GraphBinaryWriter;
 import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
 
+import java.util.List;
+
 public class ByteCodeSerializer extends SimpleTypeSerializer<Bytecode> {
 
     @Override
@@ -31,23 +37,65 @@ public class ByteCodeSerializer extends 
SimpleTypeSerializer<Bytecode> {
 
         final int stepsLength = buffer.readInt();
         for (int i = 0; i < stepsLength; i++) {
-            result.addStep(context.readValue(buffer, String.class), 
getValues(buffer, context));
+            result.addStep(context.readValue(buffer, String.class, false), 
getInstructionArguments(buffer, context));
         }
 
         final int sourcesLength = buffer.readInt();
         for (int i = 0; i < sourcesLength; i++) {
-            result.addSource(context.readValue(buffer, String.class), 
getValues(buffer, context));
+            result.addSource(context.readValue(buffer, String.class, false), 
getInstructionArguments(buffer, context));
         }
 
         return result;
     }
 
-    private static Object[] getValues(ByteBuf buffer, GraphBinaryReader 
context) throws SerializationException {
+    private static Object[] getInstructionArguments(ByteBuf buffer, 
GraphBinaryReader context) throws SerializationException {
         final int valuesLength = buffer.readInt();
         Object[] values = new Object[valuesLength];
         for (int j = 0; j < valuesLength; j++) {
-            values[j] = context.readFullyQualifiedObject(buffer);
+            values[j] = context.read(buffer);
         }
         return values;
     }
+
+    @Override
+    DataType getDataType() {
+        return DataType.BYTECODE;
+    }
+
+    @Override
+    public ByteBuf writeValueSequence(Bytecode value, ByteBufAllocator 
allocator, GraphBinaryWriter context) throws SerializationException {
+        final List<Bytecode.Instruction> steps = value.getStepInstructions();
+        final List<Bytecode.Instruction> sources = 
value.getSourceInstructions();
+        // 2 buffers for the length + plus 2 buffers per each step and source
+        final CompositeByteBuf result = allocator.compositeBuffer(2 + 
steps.size() * 2 + sources.size() * 2);
+
+        writeInstructions(allocator, context, steps, result);
+        writeInstructions(allocator, context, sources, result);
+
+        return result;
+    }
+
+    private void writeInstructions(ByteBufAllocator allocator, 
GraphBinaryWriter context,
+                                   List<Bytecode.Instruction> instructions, 
CompositeByteBuf result) throws SerializationException {
+
+        result.addComponent(true, context.writeValue(instructions.size(), 
allocator, false));
+
+        for (Bytecode.Instruction instruction : instructions) {
+            result.addComponents(
+                    true,
+                    context.writeValue(instruction.getOperator(), allocator, 
false),
+                    getArgumentsBuffer(instruction.getArguments(), allocator, 
context));
+        }
+    }
+
+    private static ByteBuf getArgumentsBuffer(Object[] arguments, 
ByteBufAllocator allocator, GraphBinaryWriter context) throws 
SerializationException {
+        final CompositeByteBuf result = allocator.compositeBuffer(1 + 
arguments.length);
+        result.addComponent(true, context.writeValue(arguments.length, 
allocator, false));
+
+        for (Object value : arguments) {
+            result.addComponent(true, context.write(value, allocator));
+        }
+
+        return result;
+    }
 }
diff --git 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/MapSerializer.java
 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/MapSerializer.java
index a9b3ad7..4719d83 100644
--- 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/MapSerializer.java
+++ 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/MapSerializer.java
@@ -19,20 +19,43 @@
 package org.apache.tinkerpop.gremlin.driver.ser.binary.types;
 
 import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.CompositeByteBuf;
 import org.apache.tinkerpop.gremlin.driver.ser.SerializationException;
+import org.apache.tinkerpop.gremlin.driver.ser.binary.DataType;
 import org.apache.tinkerpop.gremlin.driver.ser.binary.GraphBinaryReader;
+import org.apache.tinkerpop.gremlin.driver.ser.binary.GraphBinaryWriter;
 
 import java.util.HashMap;
-import java.util.Map;
 
-public class MapSerializer extends SimpleTypeSerializer<Map> {
+public class MapSerializer extends SimpleTypeSerializer<HashMap> {
     @Override
-    public Map readValue(ByteBuf buffer, GraphBinaryReader context) throws 
SerializationException {
-        final Long length = buffer.readUnsignedInt();
+    public HashMap readValue(ByteBuf buffer, GraphBinaryReader context) throws 
SerializationException {
+        final long length = buffer.readUnsignedInt();
 
-        Map result = new HashMap<>();
-        for (int i = 0; i < length; i++) {
-            result.put(context.readFullyQualifiedObject(buffer), 
context.readFullyQualifiedObject(buffer));
+        HashMap result = new HashMap<>();
+        for (long i = 0; i < length; i++) {
+            result.put(context.read(buffer), context.read(buffer));
+        }
+
+        return result;
+    }
+
+    @Override
+    DataType getDataType() {
+        return DataType.MAP;
+    }
+
+    @Override
+    public ByteBuf writeValueSequence(HashMap value, ByteBufAllocator 
allocator, GraphBinaryWriter context) throws SerializationException {
+        CompositeByteBuf result = allocator.compositeBuffer(1 + value.size() * 
2);
+        result.addComponent(true, allocator.buffer(4).writeInt(value.size()));
+
+        for (Object key : value.keySet()) {
+            result.addComponents(
+                    true,
+                    context.write(key, allocator),
+                    context.write(value.get(key), allocator));
         }
 
         return result;
diff --git 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/RequestMessageSerializer.java
 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/RequestMessageSerializer.java
index 2511f3a..92f7d3e 100644
--- 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/RequestMessageSerializer.java
+++ 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/RequestMessageSerializer.java
@@ -19,9 +19,11 @@
 package org.apache.tinkerpop.gremlin.driver.ser.binary.types;
 
 import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
 import org.apache.tinkerpop.gremlin.driver.message.RequestMessage;
 import org.apache.tinkerpop.gremlin.driver.ser.SerializationException;
 import org.apache.tinkerpop.gremlin.driver.ser.binary.GraphBinaryReader;
+import org.apache.tinkerpop.gremlin.driver.ser.binary.GraphBinaryWriter;
 import org.apache.tinkerpop.gremlin.driver.ser.binary.TypeSerializer;
 
 import java.util.HashMap;
@@ -29,23 +31,36 @@ import java.util.Map;
 import java.util.UUID;
 
 public class RequestMessageSerializer implements 
TypeSerializer<RequestMessage> {
-    public RequestMessage readValue(ByteBuf buffer, GraphBinaryReader context) 
throws SerializationException {
-        final byte version = buffer.readByte();
-        assert version >>> 7 == 1;
+    public RequestMessage readValue(ByteBuf buffer, GraphBinaryReader context, 
boolean nullable) throws SerializationException {
+        final int version = buffer.readByte();
+        assert version >>> 31 == 1;
 
-        final UUID id = context.readValue(buffer, UUID.class);
-        final String op = context.readValue(buffer, String.class);
-        final String processor = context.readValue(buffer, String.class);
+        final UUID id = context.readValue(buffer, UUID.class, false);
+        final String op = context.readValue(buffer, String.class, false);
+        final String processor = context.readValue(buffer, String.class, 
false);
 
         final RequestMessage.Builder builder = 
RequestMessage.build(op).overrideRequestId(id).processor(processor);
 
-        final Map<String, Object> args = context.readValue(buffer, Map.class);
+        final Map<String, Object> args = context.readValue(buffer, Map.class, 
false);
         args.forEach(builder::addArg);
 
         return builder.create();
     }
 
     @Override
+    public ByteBuf write(RequestMessage value, ByteBufAllocator allocator, 
GraphBinaryWriter context) {
+        //TODO: Implement
+        return null;
+    }
+
+    @Override
+    public ByteBuf writeValue(RequestMessage value, ByteBufAllocator 
allocator, GraphBinaryWriter context,
+                              boolean nullable) {
+        //TODO: Implement
+        return null;
+    }
+
+    @Override
     public RequestMessage read(ByteBuf buffer, GraphBinaryReader context) 
throws SerializationException {
         // There is no type code / information for the request message itself.
         throw new SerializationException("RequestMessageSerializer must not 
invoked with type information");
diff --git 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/SimpleTypeSerializer.java
 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/SimpleTypeSerializer.java
index cc7abd3..c8268c0 100644
--- 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/SimpleTypeSerializer.java
+++ 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/SimpleTypeSerializer.java
@@ -19,18 +19,75 @@
 package org.apache.tinkerpop.gremlin.driver.ser.binary.types;
 
 import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.CompositeByteBuf;
+import io.netty.buffer.Unpooled;
 import org.apache.tinkerpop.gremlin.driver.ser.SerializationException;
+import org.apache.tinkerpop.gremlin.driver.ser.binary.DataType;
 import org.apache.tinkerpop.gremlin.driver.ser.binary.GraphBinaryReader;
+import org.apache.tinkerpop.gremlin.driver.ser.binary.GraphBinaryWriter;
 import org.apache.tinkerpop.gremlin.driver.ser.binary.TypeSerializer;
 
+/**
+ * Base class for serialization of types that don't contain type specific 
information only {type_code}, {value_flag}
+ * and {value}.
+ */
 public abstract class SimpleTypeSerializer<T> implements TypeSerializer<T> {
+    abstract DataType getDataType();
+
     @Override
     public T read(ByteBuf buffer, GraphBinaryReader context) throws 
SerializationException {
         // No {type_info}, just {value_flag}{value}
-        final byte valueFlag = buffer.readByte();
-        if ((valueFlag & 1) == 1) {
-            return null;
+        return readValue(buffer, context, true);
+    }
+
+    @Override
+    public T readValue(ByteBuf buffer, GraphBinaryReader context, boolean 
nullable) throws SerializationException {
+        if (nullable) {
+            final byte valueFlag = buffer.readByte();
+            if ((valueFlag & 1) == 1) {
+                return null;
+            }
         }
+
         return readValue(buffer, context);
     }
+
+    /**
+     * Reads a non-nullable value
+     */
+    abstract T readValue(ByteBuf buffer, GraphBinaryReader context) throws 
SerializationException;
+
+    @Override
+    public ByteBuf write(T value, ByteBufAllocator allocator, 
GraphBinaryWriter context) throws SerializationException {
+        final ByteBuf valueBuffer = writeValue(value, allocator, context, 
true);
+
+        return allocator.compositeBuffer(2)
+                .addComponents(
+                        true,
+                        //TODO: Reuse buffer pooled locally
+                        
allocator.buffer(1).writeByte(getDataType().getCodeByte()),
+                        valueBuffer);
+    }
+
+    @Override
+    public ByteBuf writeValue(T value, ByteBufAllocator allocator, 
GraphBinaryWriter context, boolean nullable) throws SerializationException {
+        if (value == null) {
+            if (!nullable) {
+                throw new SerializationException("Unexpected null value when 
nullable is false");
+            }
+
+            return context.getValueFlagNull();
+        }
+
+        final ByteBuf valueSequence = writeValueSequence(value, allocator, 
context);
+
+        if (!nullable) {
+            return valueSequence;
+        }
+
+        return allocator.compositeBuffer(2).addComponents(true, 
context.getValueFlagNone(), valueSequence);
+    }
+
+    public abstract ByteBuf writeValueSequence(T value, ByteBufAllocator 
allocator, GraphBinaryWriter context) throws SerializationException;
 }
diff --git 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/SingleTypeSerializer.java
 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/SingleTypeSerializer.java
index d8e0e0a..b730adf 100644
--- 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/SingleTypeSerializer.java
+++ 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/SingleTypeSerializer.java
@@ -19,25 +19,56 @@
 package org.apache.tinkerpop.gremlin.driver.ser.binary.types;
 
 import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
 import org.apache.tinkerpop.gremlin.driver.ser.SerializationException;
+import org.apache.tinkerpop.gremlin.driver.ser.binary.DataType;
 import org.apache.tinkerpop.gremlin.driver.ser.binary.GraphBinaryReader;
+import org.apache.tinkerpop.gremlin.driver.ser.binary.GraphBinaryWriter;
+
+import java.util.function.BiConsumer;
 import java.util.function.Function;
 
+/**
+ * Represents a serializer for types that be represented as a single value and 
that can be read and write
+ * in a single operation.
+ */
 public class SingleTypeSerializer<T> extends SimpleTypeSerializer<T> {
-    public static final SingleTypeSerializer<Integer> IntSerializer = new 
SingleTypeSerializer<>(ByteBuf::readInt);
-    public static final SingleTypeSerializer<Long> LongSerializer = new 
SingleTypeSerializer<>(ByteBuf::readLong);
-    public static final SingleTypeSerializer<Double> DoubleSerializer = new 
SingleTypeSerializer<>(ByteBuf::readDouble);
-    public static final SingleTypeSerializer<Float> FloatSerializer = new 
SingleTypeSerializer<>(ByteBuf::readFloat);
-
-    private final Function<ByteBuf, T> func;
+    public static final SingleTypeSerializer<Integer> IntSerializer =
+            new SingleTypeSerializer<>(4, DataType.INT, ByteBuf::readInt, (v, 
b) -> b.writeInt(v));
+    public static final SingleTypeSerializer<Long> LongSerializer =
+            new SingleTypeSerializer<>(8, DataType.LONG, ByteBuf::readLong, 
(v, b) -> b.writeLong(v));
+    public static final SingleTypeSerializer<Double> DoubleSerializer =
+            new SingleTypeSerializer<>(8, DataType.DOUBLE, 
ByteBuf::readDouble, (v, b) -> b.writeDouble(v));
+    public static final SingleTypeSerializer<Float> FloatSerializer =
+            new SingleTypeSerializer<>(4, DataType.FLOAT, ByteBuf::readFloat, 
(v, b) -> b.writeFloat(v));
 
+    private final int byteLength;
+    private final DataType dataType;
+    private final Function<ByteBuf, T> readFunc;
+    private final BiConsumer<T, ByteBuf> writeFunc;
 
-    public SingleTypeSerializer(Function<ByteBuf, T> func) {
-        this.func = func;
+    private SingleTypeSerializer(int byteLength, DataType dataType, 
Function<ByteBuf, T> readFunc,
+                                 BiConsumer<T, ByteBuf> writeFunc) {
+        this.byteLength = byteLength;
+        this.dataType = dataType;
+        this.readFunc = readFunc;
+        this.writeFunc = writeFunc;
     }
 
     @Override
     public T readValue(ByteBuf buffer, GraphBinaryReader context) throws 
SerializationException {
-        return func.apply(buffer);
+        return readFunc.apply(buffer);
+    }
+
+    @Override
+    DataType getDataType() {
+        return dataType;
+    }
+
+    @Override
+    public ByteBuf writeValueSequence(T value, ByteBufAllocator allocator, 
GraphBinaryWriter context) {
+        ByteBuf buffer = allocator.buffer(byteLength);
+        writeFunc.accept(value, buffer);
+        return buffer;
     }
 }
diff --git 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/StringSerializer.java
 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/StringSerializer.java
index b0d5d84..4addf9b 100644
--- 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/StringSerializer.java
+++ 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/StringSerializer.java
@@ -19,10 +19,12 @@
 package org.apache.tinkerpop.gremlin.driver.ser.binary.types;
 
 import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
 import org.apache.tinkerpop.gremlin.driver.ser.SerializationException;
+import org.apache.tinkerpop.gremlin.driver.ser.binary.DataType;
 import org.apache.tinkerpop.gremlin.driver.ser.binary.GraphBinaryReader;
+import org.apache.tinkerpop.gremlin.driver.ser.binary.GraphBinaryWriter;
 
-import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 
 public class StringSerializer extends SimpleTypeSerializer<String> {
@@ -31,4 +33,15 @@ public class StringSerializer extends 
SimpleTypeSerializer<String> {
         final int length = buffer.readInt();
         return buffer.readCharSequence(length, 
StandardCharsets.UTF_8).toString();
     }
+
+    @Override
+    DataType getDataType() {
+        return DataType.STRING;
+    }
+
+    @Override
+    public ByteBuf writeValueSequence(String value, ByteBufAllocator 
allocator, GraphBinaryWriter context) {
+        final byte[] stringBytes = value.getBytes(StandardCharsets.UTF_8);
+        return allocator.buffer(4 + 
stringBytes.length).writeInt(stringBytes.length).writeBytes(stringBytes);
+    }
 }
diff --git 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/UUIDSerializer.java
 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/UUIDSerializer.java
index ca7fbce..80e40af 100644
--- 
a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/UUIDSerializer.java
+++ 
b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/binary/types/UUIDSerializer.java
@@ -19,8 +19,11 @@
 package org.apache.tinkerpop.gremlin.driver.ser.binary.types;
 
 import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
 import org.apache.tinkerpop.gremlin.driver.ser.SerializationException;
+import org.apache.tinkerpop.gremlin.driver.ser.binary.DataType;
 import org.apache.tinkerpop.gremlin.driver.ser.binary.GraphBinaryReader;
+import org.apache.tinkerpop.gremlin.driver.ser.binary.GraphBinaryWriter;
 
 import java.util.UUID;
 
@@ -29,4 +32,16 @@ public class UUIDSerializer extends 
SimpleTypeSerializer<UUID> {
     public UUID readValue(ByteBuf buffer, GraphBinaryReader context) throws 
SerializationException {
         return new UUID(buffer.readLong(), buffer.readLong());
     }
+
+    @Override
+    DataType getDataType() {
+        return DataType.UUID;
+    }
+
+    @Override
+    public ByteBuf writeValueSequence(UUID value, ByteBufAllocator allocator, 
GraphBinaryWriter context) {
+        return allocator.buffer(16)
+                .writeLong(value.getMostSignificantBits())
+                .writeLong(value.getLeastSignificantBits());
+    }
 }
\ No newline at end of file
diff --git 
a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ser/GraphBinaryReaderWriterRoundTripTest.java
 
b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ser/GraphBinaryReaderWriterRoundTripTest.java
new file mode 100644
index 0000000..bef6dfd
--- /dev/null
+++ 
b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/ser/GraphBinaryReaderWriterRoundTripTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.driver.ser;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.driver.ser.binary.*;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.*;
+
+@RunWith(Parameterized.class)
+public class GraphBinaryReaderWriterRoundTripTest {
+    private final GraphBinaryWriter writer = new GraphBinaryWriter();
+    private final GraphBinaryReader reader = new GraphBinaryReader();
+    private final ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
+    private final Object value;
+
+    public GraphBinaryReaderWriterRoundTripTest(Object value) {
+        this.value = value;
+    }
+
+    @Parameterized.Parameters
+    public static Collection input() {
+        Bytecode bytecode = new Bytecode();
+        bytecode.addStep("V");
+        bytecode.addStep("tail", 3);
+        bytecode.addSource(TraversalSource.Symbols.withComputer, "myComputer");
+
+        Map<String, Integer> map = new HashMap<>();
+        map.put("one", 1);
+        map.put("two", 2);
+
+        return Arrays.asList("ABC", 1, 2f, 3.1d, 10122L, UUID.randomUUID(), 
bytecode, map);
+    }
+
+    @Test
+    public void shouldWriteAndRead() throws Exception {
+        ByteBuf buffer = writer.write(value, allocator);
+        buffer.readerIndex(0);
+        Object result = reader.read(buffer);
+        Assert.assertEquals(value, result);
+    }
+}

Reply via email to