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); + } +}