Protobuf: Fixed stub in length-delimited fields serialization. Now related methods write size of object correctly. Added necessary methods for estimating byte-size of objects in run-time in ProtoKot runtime-library.

This commit is contained in:
dsavvinov
2016-07-18 16:05:56 +03:00
parent f6790e43ae
commit 8552e0b778
13 changed files with 548 additions and 176 deletions
@@ -26,4 +26,9 @@ clean:
generate:
./protoc --kotlin_out=./ ./test/addressbook.proto
.PHONY: clean all generate
.PHONY: clean all generate
low: dummy
dummy: CXXFLAGS += -DKOTLIN_GENERATED_CODE_LANGUAGE_LEVEL_LOW
dummy: all
@@ -48,8 +48,6 @@ void ClassGenerator::generateCode(io::Printer *printer, bool isBuilder) const {
// write serialization methods only for fair classes, read methods only for Builders)
printer->Print("\n");
generateSerializers(printer, /* isRead = */ isBuilder);
printer->Print("\n");
generateSerializersNoTag(printer, /* isRead = */ isBuilder);
// builder, mergeFrom and only for fair classes
if (!isBuilder) {
@@ -69,6 +67,9 @@ void ClassGenerator::generateCode(io::Printer *printer, bool isBuilder) const {
generateParseMethods(printer);
}
// getSize()
generateGetSizeMethod(printer);
printer->Outdent();
printer->Print("}\n");
}
@@ -172,32 +173,10 @@ void ClassGenerator::generateMergeMethods(io::Printer *printer) const {
printer->Print("}\n");
}
void ClassGenerator::generateSerializers(io::Printer * printer, bool isRead) const {
// readFrom(input: CodedInputStream) OR
// writeTo(output: CodedOutputStream)
void ClassGenerator::generateSerializers(io::Printer *printer, bool isRead) const {
map <string, string> vars;
vars["funName"]= isRead ? "readFrom" : "writeTo";
vars["returnType"] = isRead ? builderName : "Unit";
vars["stream"] = isRead ? "CodedInputStream" : "CodedOutputStream";
vars["arg"] = isRead ? "input" : "output";
vars["maybeSeparator"] = isRead ? "" : ", ";
vars["maybeReturn"] = isRead ? "return " : "";
// generate function header
printer->Print(vars,
"fun $funName$ ($arg$: $stream$): $returnType$ {"
"\n");
printer->Indent();
printer->Print(vars, "$maybeReturn$$funName$NoTag($arg$)\n");
printer->Outdent();
printer->Print("}\n");
}
void ClassGenerator::generateSerializersNoTag(io::Printer *printer, bool isRead) const {
map <string, string> vars;
vars["funName"]= isRead ? "readFromNoTag" : "writeToNoTag";
vars["funName"]= isRead ? "readFrom" : "writeTo";
vars["stream"] = isRead ? "CodedInputStream" : "CodedOutputStream";
vars["arg"] = isRead ? "input" : "output";
vars["returnType"] = isRead ? builderName : "Unit";
@@ -341,6 +320,30 @@ void ClassGenerator::generateParseMethods(io::Printer *printer) const {
printer->Print("}\n");
}
void ClassGenerator::generateGetSizeMethod(io::Printer *printer) const {
printer->Print("fun getSize(): Int {\n");
printer->Indent();
printer->Print("var size = 0\n");
for (int i = 0; i < properties.size(); ++i) {
properties[i]->generateSizeEstimationCode(printer, "size");
}
printer->Print("return size\n");
printer->Outdent();
printer->Print("}\n");
}
//int ClassGenerator::getSizeWithoutHeader() {
// int size = 0;
// for (int i = 0; i < properties.size(); ++i) {
// size += properties[i].getSizeWithHeader();
// }
// return 0;
//}
const string ClassModifier::getName() const {
string result = "";
@@ -62,10 +62,10 @@ private:
* both methods are structurally the same with some trivial substitutions
* (read -> write and etc.)
*/
void generateSerializersNoTag(io::Printer *printer, bool isRead = false) const;
void generateSerializers(io::Printer * printer, bool isRead = false) const;
void generateMergeMethods(io::Printer *printer) const;
void generateParseMethods(io::Printer * printer) const;
void generateGetSizeMethod(io::Printer * printer) const;
};
} // namespace kotlin
@@ -16,12 +16,10 @@ namespace kotlin {
string FieldGenerator::getInitValue() const {
if (descriptor->is_repeated())
#ifndef KOTLIN_GENERATED_CODE_LANGUAGE_LEVEL_LOW
return "mutableListOf()";
#else
return "arrayOf()";
#endif
else if (protoType == FieldDescriptor::TYPE_MESSAGE) {
return enclosingClass->builderName + "().build()";
}
return name_resolving::protobufTypeToInitValue(descriptor);
}
@@ -82,8 +80,9 @@ void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead
if (!noTag) {
printer->Print(vars, "val tag = input.readTag($fieldNumber$, WireType.LENGTH_DELIMITED)\n");
}
printer->Print(vars, "val listSize = input.readInt32NoTag()\n");
printer->Print(vars, "for (i in 1..listSize) {\n");
printer->Print(vars, "val expectedSize = input.readInt32NoTag()\n");
printer->Print("var readSize = 0\n");
printer->Print(vars, "while(readSize != expectedSize) {\n");
printer->Indent();
/* hack: copy current FieldGenerator and change label to OPTIONAL. Also change name to
@@ -99,17 +98,25 @@ void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead
/* Another dirty hack here: create tmp variable of a given type and read it from input stream
then add that tmp var into list.
This is made because simple recursive call will generate code, that tries to array[i].mergeFrom()
but this is incorrect, because array has old size, while 'i' iterates over new size, and of course
they are not necessary the same.
This is made because simple recursive call will generate code that tries to array[i].mergeFrom().
This is incorrect because array has old size, while 'i' iterates over new size, which can lead
to ArrayOutOfIndex errors.
*/
// TODO: stub here, resolve name properly!
vars["fieldType"] = underlyingType + ".Builder" + underlyingType;
vars["initValue"] = getUnderlyingTypeInitValue();
printer->Print(vars, "val tmp: $fieldType$ = $fieldType$()\n");
vars["initValue"] = underlyingType + ".Builder" + underlyingType + "()";
printer->Print(vars, "val tmp: $fieldType$ = $initValue$\n");
singleFieldGen.simpleName = "tmp";
singleFieldGen.modifier = FieldDescriptor::LABEL_OPTIONAL;
singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ true);
// Note that primitive types are packed by default in proto3, i.e. they are should be written without tag
bool isPrimitive = descriptor->type() != FieldDescriptor::TYPE_BYTES &&
descriptor->type() != FieldDescriptor::TYPE_MESSAGE &&
descriptor->type() != FieldDescriptor::TYPE_STRING &&
descriptor->type() != FieldDescriptor::TYPE_ENUM;
singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ isPrimitive);
singleFieldGen.generateSizeEstimationCode(printer, "readSize"); // add size of current element to total size
printer->Print(vars, "$fieldName$.add(tmp.build())\n");
@@ -124,7 +131,9 @@ void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead
printer->Print(vars, "output.writeTag($fieldNumber$, WireType.LENGTH_DELIMITED)\n");
// length
printer->Print(vars, "output.writeInt32NoTag($fieldName$.size)\n");
printer->Print(vars, "var arrayByteSize = 0\n");
generateSizeEstimationCode(printer, "arrayByteSize", /* noTag = */ true);
printer->Print(vars, "output.writeInt32NoTag(arrayByteSize)\n");
// all elements
printer->Print(vars, "for (item in $fieldName$) {\n");
@@ -134,7 +143,15 @@ void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead
FieldGenerator singleFieldGen = FieldGenerator(descriptor, enclosingClass);
singleFieldGen.simpleName = "item";
singleFieldGen.modifier = FieldDescriptor::LABEL_OPTIONAL;
singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ true);
// TODO: maybe refactor this in name_resolving or separate method at least
// Note that primitive types are packed by default in proto3, i.e. they are should be written without tag
bool isPrimitive = descriptor->type() != FieldDescriptor::TYPE_BYTES &&
descriptor->type() != FieldDescriptor::TYPE_MESSAGE &&
descriptor->type() != FieldDescriptor::TYPE_STRING &&
descriptor->type() != FieldDescriptor::TYPE_ENUM;
singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ isPrimitive);
printer->Outdent(); // for-loop
printer->Print("}\n");
@@ -166,17 +183,42 @@ void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead
/*
Then check for nested messages. Then we re-use writeTo method, that should be defined in
that message
that message.
Note that readFrom/writeTo methods write message as it's top-level message, i.e. without
any tags. Therefore, we have to prepend tags and size manually.
*/
if (descriptor->type() == FieldDescriptor::TYPE_MESSAGE) {
vars["maybeNoTag"] = "NoTag";
if (isRead) {
vars["fieldNumber"] = std::to_string(fieldNumber);
vars["dollar"] = "$";
// read tag
printer->Print(vars, "input.readTag($fieldNumber$, WireType.LENGTH_DELIMITED)\n");
// read expected size
printer->Print(vars, "val expectedSize = input.readInt32NoTag()\n");
// read message itself without tag
printer->Print(vars,
"$fieldName$.readFrom$maybeNoTag$(input)\n");
"$fieldName$.readFrom(input)\n");
// check that actual size equal to expected size
printer->Print(vars, "if (expectedSize != $fieldName$.getSize()) { "
"throw InvalidProtocolBufferException ("
"\"Expected size $dollar${expectedSize} got $dollar${$fieldName$.getSize()}"
"\") }\n");
}
else {
vars["fieldNumber"] = std::to_string(fieldNumber);
// write tag
printer->Print(vars, "output.writeTag($fieldNumber$, WireType.LENGTH_DELIMITED)\n");
// write message length via runtime-call
printer->Print(vars, "output.writeInt32NoTag($fieldName$.getSize())\n");
// write message itself without tag
printer->Print(vars,
"$fieldName$.writeTo$maybeNoTag$(output)\n");
"$fieldName$.writeTo(output)\n");
}
return;
}
@@ -224,7 +266,6 @@ void FieldGenerator::generateRepeatedMethods(io::Printer * printer, bool isBuild
printer->Print("}\n");
}
#ifndef KOTLIN_GENERATED_CODE_LANGUAGE_LEVEL_LOW
if (isBuilder) {
// generate single-add for builders
printer->Print(vars, "fun add$elementType$($arg$: $elementType$): $builderName$ {\n");
@@ -249,7 +290,6 @@ void FieldGenerator::generateRepeatedMethods(io::Printer * printer, bool isBuild
printer->Outdent(); // function body
printer->Print("}\n");
}
#endif
}
string FieldGenerator::getKotlinFunctionSuffix() const {
@@ -257,9 +297,85 @@ string FieldGenerator::getKotlinFunctionSuffix() const {
}
string FieldGenerator::getUnderlyingTypeInitValue() const {
if (protoType == FieldDescriptor::TYPE_MESSAGE) {
return "Builder" + underlyingType + "().build()";
}
return name_resolving::protobufTypeToInitValue(descriptor);
}
void FieldGenerator::generateSizeEstimationCode(io::Printer *printer, string varName, bool noTag) const {
map<string, string> vars;
vars["varName"] = varName;
vars["fieldName"] = simpleName;
vars["fieldNumber"] = std::to_string(fieldNumber);
// First of all, generate code for repeated fields
if (modifier == FieldDescriptor::LABEL_REPEATED) {
// We will need total byte size of array, because that size is itself a part of the message and
// adds to total message size.
// For the sake of hygiene, temporary variables are created in anonymous scope
printer->Print("run {\n");
printer->Indent();
// Create a temporary variable that will collect array byte size
printer->Print("var arraySize = 0\n");
// iterate over all elements of array
printer->Print(vars, "for (item in $fieldName$) {\n");
printer->Indent();
// hack: reuse generateSizeEstimationCode in the same manner as in generateSerializationCode
FieldGenerator singleFieldGen = FieldGenerator(descriptor, enclosingClass);
singleFieldGen.modifier = FieldDescriptor::LABEL_OPTIONAL;
singleFieldGen.simpleName = "item";
singleFieldGen.generateSizeEstimationCode(printer, "arraySize");
printer->Outdent(); // for-loop
printer->Print("}\n");
// now add to total message size size of array, consisting of:
printer->Print(vars,
"$varName$ += arraySize"); // actual array size
if (!noTag) {
printer->Print(vars,
" + "
"WireFormat.getTagSize($fieldNumber$, WireType.LENGTH_DELIMITED)" // tag size
" + "
"WireFormat.getVarint32Size(arraySize)"); // runtime call, that will get size of varint, denoting size of array
}
printer->Print("\n");
printer->Outdent(); // anonymous scope
printer->Print("}\n");
return;
}
// Then, call getSize recursively for nested messages
// TODO: currently suboptimal repeatative calls getSize() are being made. We can optimize it later via caching calls to getSize()
if (protoType == FieldDescriptor::TYPE_MESSAGE) {
// don't forget about tag and length annotation
printer->Print(vars, "$varName$ += $fieldName$.getSize()"
" + "
"WireFormat.getTagSize($fieldNumber$, WireType.LENGTH_DELIMITED)"
" + "
"WireFormat.getVarint32Size($fieldName$.getSize())\n"
);
return;
}
// Next, process enums as they should be casted to ints manually
if (protoType == FieldDescriptor::TYPE_ENUM) {
vars["enumName"] = fullType;
printer->Print(vars, "$varName$ += WireFormat.getEnumSize($fieldNumber$, $fieldName$.ord)\n");
return;
}
// Finally, get size of all primitive types trivially via call to WireFormat in runtime
vars["kotlinSuffix"] = name_resolving::protobufTypeToKotlinFunctionSuffix(descriptor->type());
printer->Print(vars, "$varName$ += WireFormat.get$kotlinSuffix$Size($fieldNumber$, $fieldName$)\n");
return;
}
} // namespace kotlin
} // namespace compiler
} // namespace protobuf
@@ -45,6 +45,7 @@ public:
void generateCode(io::Printer * printer, bool isBuilder) const;
void generateSerializationCode(io::Printer * printer, bool isRead = false, bool noTag = false) const;
void generateSizeEstimationCode(io::Printer * printer, string varName, bool noTag = false) const;
FieldGenerator(FieldDescriptor const * descriptor, ClassGenerator const * enclosingClass);
string getKotlinFunctionSuffix() const;
@@ -83,19 +83,14 @@ string protobufToKotlinField(FieldDescriptor const * descriptor) {
case FieldDescriptor::LABEL_OPTIONAL:
break;
case FieldDescriptor::LABEL_REPEATED:
#ifndef KOTLIN_GENERATED_CODE_LANGUAGE_LEVEL_LOW
preamble = "MutableList <";
postamble = "> ";
break;
#else
preamble = "Array <";
postamble = "> ";
break;
#endif
}
return preamble + protobufToKotlinType(descriptor) + postamble;
}
// TODO: think about nested arrays
string protobufTypeToInitValue(FieldDescriptor const * descriptor) {
FieldDescriptor::Type type = descriptor->type();
switch(type) {
@@ -10,7 +10,6 @@
#include "kotlin_file_generator.h"
#include <iostream>
//#define KOTLIN_GENERATED_CODE_LANGUAGE_LEVEL_LOW
int main(int argc, const char* const * argv) {
google::protobuf::compiler::CommandLineInterface cli;
@@ -52,10 +52,6 @@ class Person private constructor (name: kotlin.String = "", id: Int = 0, email:
}
fun writeTo (output: CodedOutputStream): Unit {
writeToNoTag(output)
}
fun writeToNoTag (output: CodedOutputStream): Unit {
output.writeString (1, number)
output.writeEnum (2, type.ord)
}
@@ -82,10 +78,6 @@ class Person private constructor (name: kotlin.String = "", id: Int = 0, email:
}
fun readFrom (input: CodedInputStream): BuilderPhoneNumber {
return readFromNoTag(input)
}
fun readFromNoTag (input: CodedInputStream): BuilderPhoneNumber {
number = input.readString(1)
type = PhoneType.fromIntToPhoneType(input.readEnum(2))
return this
@@ -110,6 +102,12 @@ class Person private constructor (name: kotlin.String = "", id: Int = 0, email:
while(parseFieldFrom(input)) {}
return this
}
fun getSize(): Int {
var size = 0
size += WireFormat.getStringSize(1, number)
size += WireFormat.getEnumSize(2, type.ord)
return size
}
}
@@ -121,22 +119,34 @@ class Person private constructor (name: kotlin.String = "", id: Int = 0, email:
fun mergeFrom (input: CodedInputStream) {
val builder = BuilderPhoneNumber()
mergeWith(builder.parseFrom(input).build())}
fun getSize(): Int {
var size = 0
size += WireFormat.getStringSize(1, number)
size += WireFormat.getEnumSize(2, type.ord)
return size
}
}
fun writeTo (output: CodedOutputStream): Unit {
writeToNoTag(output)
}
fun writeToNoTag (output: CodedOutputStream): Unit {
output.writeString (1, name)
output.writeInt32 (2, id)
output.writeString (3, email)
if (phones.size > 0) {
output.writeTag(4, WireType.LENGTH_DELIMITED)
output.writeInt32NoTag(phones.size)
var arrayByteSize = 0
run {
var arraySize = 0
for (item in phones) {
arraySize += item.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize())
}
arrayByteSize += arraySize
}
output.writeInt32NoTag(arrayByteSize)
for (item in phones) {
item.writeToNoTag(output)
output.writeTag(4, WireType.LENGTH_DELIMITED)
output.writeInt32NoTag(item.getSize())
item.writeTo(output)
}
}
output.writeBytes (5, someBytes)
@@ -202,18 +212,19 @@ class Person private constructor (name: kotlin.String = "", id: Int = 0, email:
}
fun readFrom (input: CodedInputStream): BuilderPerson {
return readFromNoTag(input)
}
fun readFromNoTag (input: CodedInputStream): BuilderPerson {
name = input.readString(1)
id = input.readInt32(2)
email = input.readString(3)
val tag = input.readTag(4, WireType.LENGTH_DELIMITED)
val listSize = input.readInt32NoTag()
for (i in 1..listSize) {
val expectedSize = input.readInt32NoTag()
var readSize = 0
while(readSize != expectedSize) {
val tmp: PhoneNumber.BuilderPhoneNumber = PhoneNumber.BuilderPhoneNumber()
tmp.readFromNoTag(input)
input.readTag(4, WireType.LENGTH_DELIMITED)
val expectedSize = input.readInt32NoTag()
tmp.readFrom(input)
if (expectedSize != tmp.getSize()) { throw InvalidProtocolBufferException ("Expected size ${expectedSize} got ${tmp.getSize()}") }
readSize += tmp.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(tmp.getSize())
phones.add(tmp.build())
}
someBytes = input.readBytes(5)
@@ -235,10 +246,15 @@ class Person private constructor (name: kotlin.String = "", id: Int = 0, email:
2 -> id = input.readInt32NoTag()
3 -> email = input.readStringNoTag()
4 -> {
val listSize = input.readInt32NoTag()
for (i in 1..listSize) {
val expectedSize = input.readInt32NoTag()
var readSize = 0
while(readSize != expectedSize) {
val tmp: PhoneNumber.BuilderPhoneNumber = PhoneNumber.BuilderPhoneNumber()
tmp.readFromNoTag(input)
input.readTag(4, WireType.LENGTH_DELIMITED)
val expectedSize = input.readInt32NoTag()
tmp.readFrom(input)
if (expectedSize != tmp.getSize()) { throw InvalidProtocolBufferException ("Expected size ${expectedSize} got ${tmp.getSize()}") }
readSize += tmp.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(tmp.getSize())
phones.add(tmp.build())
}
}
@@ -249,6 +265,21 @@ class Person private constructor (name: kotlin.String = "", id: Int = 0, email:
while(parseFieldFrom(input)) {}
return this
}
fun getSize(): Int {
var size = 0
size += WireFormat.getStringSize(1, name)
size += WireFormat.getInt32Size(2, id)
size += WireFormat.getStringSize(3, email)
run {
var arraySize = 0
for (item in phones) {
arraySize += item.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize())
}
size += arraySize + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(arraySize)
}
size += WireFormat.getBytesSize(5, someBytes)
return size
}
}
@@ -263,6 +294,21 @@ class Person private constructor (name: kotlin.String = "", id: Int = 0, email:
fun mergeFrom (input: CodedInputStream) {
val builder = BuilderPerson()
mergeWith(builder.parseFrom(input).build())}
fun getSize(): Int {
var size = 0
size += WireFormat.getStringSize(1, name)
size += WireFormat.getInt32Size(2, id)
size += WireFormat.getStringSize(3, email)
run {
var arraySize = 0
for (item in phones) {
arraySize += item.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize())
}
size += arraySize + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(arraySize)
}
size += WireFormat.getBytesSize(5, someBytes)
return size
}
}
@@ -276,15 +322,21 @@ class AddressBook private constructor (people: MutableList <Person> = mutableLi
}
fun writeTo (output: CodedOutputStream): Unit {
writeToNoTag(output)
}
fun writeToNoTag (output: CodedOutputStream): Unit {
if (people.size > 0) {
output.writeTag(1, WireType.LENGTH_DELIMITED)
output.writeInt32NoTag(people.size)
var arrayByteSize = 0
run {
var arraySize = 0
for (item in people) {
arraySize += item.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize())
}
arrayByteSize += arraySize
}
output.writeInt32NoTag(arrayByteSize)
for (item in people) {
item.writeToNoTag(output)
output.writeTag(1, WireType.LENGTH_DELIMITED)
output.writeInt32NoTag(item.getSize())
item.writeTo(output)
}
}
}
@@ -317,15 +369,16 @@ class AddressBook private constructor (people: MutableList <Person> = mutableLi
}
fun readFrom (input: CodedInputStream): BuilderAddressBook {
return readFromNoTag(input)
}
fun readFromNoTag (input: CodedInputStream): BuilderAddressBook {
val tag = input.readTag(1, WireType.LENGTH_DELIMITED)
val listSize = input.readInt32NoTag()
for (i in 1..listSize) {
val expectedSize = input.readInt32NoTag()
var readSize = 0
while(readSize != expectedSize) {
val tmp: Person.BuilderPerson = Person.BuilderPerson()
tmp.readFromNoTag(input)
input.readTag(1, WireType.LENGTH_DELIMITED)
val expectedSize = input.readInt32NoTag()
tmp.readFrom(input)
if (expectedSize != tmp.getSize()) { throw InvalidProtocolBufferException ("Expected size ${expectedSize} got ${tmp.getSize()}") }
readSize += tmp.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(tmp.getSize())
people.add(tmp.build())
}
return this
@@ -343,10 +396,15 @@ class AddressBook private constructor (people: MutableList <Person> = mutableLi
val wireType = WireFormat.getTagWireType(tag)
when(fieldNumber) {
1 -> {
val listSize = input.readInt32NoTag()
for (i in 1..listSize) {
val expectedSize = input.readInt32NoTag()
var readSize = 0
while(readSize != expectedSize) {
val tmp: Person.BuilderPerson = Person.BuilderPerson()
tmp.readFromNoTag(input)
input.readTag(1, WireType.LENGTH_DELIMITED)
val expectedSize = input.readInt32NoTag()
tmp.readFrom(input)
if (expectedSize != tmp.getSize()) { throw InvalidProtocolBufferException ("Expected size ${expectedSize} got ${tmp.getSize()}") }
readSize += tmp.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(tmp.getSize())
people.add(tmp.build())
}
}
@@ -356,6 +414,17 @@ class AddressBook private constructor (people: MutableList <Person> = mutableLi
while(parseFieldFrom(input)) {}
return this
}
fun getSize(): Int {
var size = 0
run {
var arraySize = 0
for (item in people) {
arraySize += item.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize())
}
size += arraySize + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(arraySize)
}
return size
}
}
@@ -366,6 +435,17 @@ class AddressBook private constructor (people: MutableList <Person> = mutableLi
fun mergeFrom (input: CodedInputStream) {
val builder = BuilderAddressBook()
mergeWith(builder.parseFrom(input).build())}
fun getSize(): Int {
var size = 0
run {
var arraySize = 0
for (item in people) {
arraySize += item.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize())
}
size += arraySize + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(arraySize)
}
return size
}
}
+3 -7
View File
@@ -1,5 +1,8 @@
import java.nio.ByteBuffer
import java.nio.ByteOrder
import WireFormat.VARINT_INFO_BITS_COUNT
import WireFormat.VARINT_INFO_BITS_MASK
import WireFormat.VARINT_UTIL_BIT_MASK
/**
* Created by Dmitry Savvinov on 7/6/16.
@@ -13,7 +16,6 @@ import java.nio.ByteOrder
// TODO: refactor correctness checks into readTag
class CodedInputStream(input: java.io.InputStream) {
val bufferedInput: java.io.BufferedInputStream
init {
bufferedInput = java.io.BufferedInputStream(input) // TODO: Java's realization uses hand-written buffers. Why?
@@ -299,11 +301,5 @@ class CodedInputStream(input: java.io.InputStream) {
bufferedInput.reset()
return byte == -1
}
// couple of constants for magic numbers
val VARINT_INFO_BITS_COUNT: Int = 7
val VARINT_INFO_BITS_MASK: Int = 0b01111111 // mask for separating lowest 7 bits, where actual information stored
val VARINT_UTIL_BIT_MASK: Int = 0b10000000 // mask for separating highest bit, that indicates next byte presence
}
+7 -7
View File
@@ -1,5 +1,8 @@
import java.nio.ByteBuffer
import java.nio.ByteOrder
import WireFormat.VARINT_INFO_BITS_COUNT
import WireFormat.VARINT_INFO_BITS_MASK
import WireFormat.VARINT_UTIL_BIT_MASK
/**
* Created by user on 7/6/16.
@@ -75,7 +78,7 @@ class CodedOutputStream(val output: java.io.OutputStream) {
}
fun writeSInt64NoTag(value: Long) {
writeInt64NoTag((value shl 1) xor (value shr 31))
writeInt64NoTag((value shl 1) xor (value shr 63))
}
fun writeFixed32(fieldNumber: Int, value: Int?) {
@@ -148,7 +151,7 @@ class CodedOutputStream(val output: java.io.OutputStream) {
fun writeStringNoTag(value: String) {
writeInt32NoTag(value.length)
output.write(value.toByteArray())
output.write(value.toByteArray(Charsets.UTF_8))
}
fun writeBytes(fieldNumber: Int, value: ByteArray?) {
@@ -209,7 +212,7 @@ class CodedOutputStream(val output: java.io.OutputStream) {
var resSize = 0
do {
// encode current 7 bits
var curByte = (curValue and VARINT_INFO_BITS_MASK)
var curByte = (curValue and WireFormat.VARINT_INFO_BITS_MASK)
// discard encoded bits. Note that unsigned shift is needed for cases with negative numbers
curValue = curValue ushr VARINT_INFO_BITS_COUNT
@@ -251,8 +254,5 @@ class CodedOutputStream(val output: java.io.OutputStream) {
output.write(res, 0, resSize)
}
// couple of constants for magic numbers
val VARINT_INFO_BITS_COUNT: Int = 7
val VARINT_INFO_BITS_MASK: Int = 0b01111111 // mask for separating lowest 7 bits, where actual information stored
val VARINT_UTIL_BIT_MASK: Int = 0b10000000 // mask for separating highest bit, that indicates next byte presence
}
+144 -64
View File
@@ -1,17 +1,17 @@
class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email: kotlin.String? = "", phones: MutableList <PhoneNumber> = mutableListOf(), someBytes: ByteArray? = ByteArray(0)) {
var name : kotlin.String?
class Person private constructor (name: kotlin.String = "", id: Int = 0, email: kotlin.String = "", phones: MutableList <PhoneNumber> = mutableListOf(), someBytes: ByteArray = ByteArray(0)) {
var name : kotlin.String
private set
var id : Int?
var id : Int
private set
var email : kotlin.String?
var email : kotlin.String
private set
var phones : MutableList <PhoneNumber>
private set
var someBytes : ByteArray?
var someBytes : ByteArray
private set
@@ -38,11 +38,11 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
}
}
}
class PhoneNumber private constructor (number: kotlin.String? = "", type: PhoneType? = PhoneType.fromIntToPhoneType(0)) {
var number : kotlin.String?
class PhoneNumber private constructor (number: kotlin.String = "", type: PhoneType = PhoneType.fromIntToPhoneType(0)) {
var number : kotlin.String
private set
var type : PhoneType?
var type : PhoneType
private set
@@ -52,25 +52,21 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
}
fun writeTo (output: CodedOutputStream): Unit {
writeToNoTag(output)
}
fun writeToNoTag (output: CodedOutputStream): Unit {
output.writeString (1, number)
output.writeEnum (2, type?.ord)
output.writeEnum (2, type.ord)
}
class BuilderPhoneNumber constructor (number: kotlin.String? = "", type: PhoneType? = PhoneType.fromIntToPhoneType(0)) {
var number : kotlin.String?
class BuilderPhoneNumber constructor (number: kotlin.String = "", type: PhoneType = PhoneType.fromIntToPhoneType(0)) {
var number : kotlin.String
private set
fun setNumber(value: kotlin.String?): BuilderPhoneNumber {
fun setNumber(value: kotlin.String): BuilderPhoneNumber {
number = value
return this
}
var type : PhoneType?
var type : PhoneType
private set
fun setType(value: PhoneType?): BuilderPhoneNumber {
fun setType(value: PhoneType): BuilderPhoneNumber {
type = value
return this
}
@@ -82,10 +78,6 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
}
fun readFrom (input: CodedInputStream): BuilderPhoneNumber {
return readFromNoTag(input)
}
fun readFromNoTag (input: CodedInputStream): BuilderPhoneNumber {
number = input.readString(1)
type = PhoneType.fromIntToPhoneType(input.readEnum(2))
return this
@@ -110,6 +102,12 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
while(parseFieldFrom(input)) {}
return this
}
fun getSize(): Int {
var size = 0
size += WireFormat.getStringSize(1, number)
size += WireFormat.getEnumSize(2, type.ord)
return size
}
}
@@ -121,45 +119,57 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
fun mergeFrom (input: CodedInputStream) {
val builder = BuilderPhoneNumber()
mergeWith(builder.parseFrom(input).build())}
fun getSize(): Int {
var size = 0
size += WireFormat.getStringSize(1, number)
size += WireFormat.getEnumSize(2, type.ord)
return size
}
}
fun writeTo (output: CodedOutputStream): Unit {
writeToNoTag(output)
}
fun writeToNoTag (output: CodedOutputStream): Unit {
output.writeString (1, name)
output.writeInt32 (2, id)
output.writeString (3, email)
if (phones.size > 0) {
output.writeTag(4, WireType.LENGTH_DELIMITED)
output.writeInt32NoTag(phones.size)
var arrayByteSize = 0
run {
var arraySize = 0
for (item in phones) {
arraySize += item.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize())
}
arrayByteSize += arraySize
}
output.writeInt32NoTag(arrayByteSize)
for (item in phones) {
item.writeToNoTag(output)
output.writeTag(4, WireType.LENGTH_DELIMITED)
output.writeInt32NoTag(item.getSize())
item.writeTo(output)
}
}
output.writeBytes (5, someBytes)
}
class BuilderPerson constructor (name: kotlin.String? = "", id: Int? = 0, email: kotlin.String? = "", phones: MutableList <PhoneNumber> = mutableListOf(), someBytes: ByteArray? = ByteArray(0)) {
var name : kotlin.String?
class BuilderPerson constructor (name: kotlin.String = "", id: Int = 0, email: kotlin.String = "", phones: MutableList <PhoneNumber> = mutableListOf(), someBytes: ByteArray = ByteArray(0)) {
var name : kotlin.String
private set
fun setName(value: kotlin.String?): BuilderPerson {
fun setName(value: kotlin.String): BuilderPerson {
name = value
return this
}
var id : Int?
var id : Int
private set
fun setId(value: Int?): BuilderPerson {
fun setId(value: Int): BuilderPerson {
id = value
return this
}
var email : kotlin.String?
var email : kotlin.String
private set
fun setEmail(value: kotlin.String?): BuilderPerson {
fun setEmail(value: kotlin.String): BuilderPerson {
email = value
return this
}
@@ -185,9 +195,9 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
return this
}
var someBytes : ByteArray?
var someBytes : ByteArray
private set
fun setSomeBytes(value: ByteArray?): BuilderPerson {
fun setSomeBytes(value: ByteArray): BuilderPerson {
someBytes = value
return this
}
@@ -202,18 +212,19 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
}
fun readFrom (input: CodedInputStream): BuilderPerson {
return readFromNoTag(input)
}
fun readFromNoTag (input: CodedInputStream): BuilderPerson {
name = input.readString(1)
id = input.readInt32(2)
email = input.readString(3)
val tag = input.readTag(4, WireType.LENGTH_DELIMITED)
val listSize = input.readInt32NoTag()
for (i in 1..listSize) {
val expectedSize = input.readInt32NoTag()
var readSize = 0
while(readSize != expectedSize) {
val tmp: PhoneNumber.BuilderPhoneNumber = PhoneNumber.BuilderPhoneNumber()
tmp.readFromNoTag(input)
input.readTag(4, WireType.LENGTH_DELIMITED)
val expectedSize = input.readInt32NoTag()
tmp.readFrom(input)
if (expectedSize != tmp.getSize()) { throw InvalidProtocolBufferException ("Expected size ${expectedSize} got ${tmp.getSize()}") }
readSize += tmp.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(tmp.getSize())
phones.add(tmp.build())
}
someBytes = input.readBytes(5)
@@ -235,10 +246,15 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
2 -> id = input.readInt32NoTag()
3 -> email = input.readStringNoTag()
4 -> {
val listSize = input.readInt32NoTag()
for (i in 1..listSize) {
val expectedSize = input.readInt32NoTag()
var readSize = 0
while(readSize != expectedSize) {
val tmp: PhoneNumber.BuilderPhoneNumber = PhoneNumber.BuilderPhoneNumber()
tmp.readFromNoTag(input)
input.readTag(4, WireType.LENGTH_DELIMITED)
val expectedSize = input.readInt32NoTag()
tmp.readFrom(input)
if (expectedSize != tmp.getSize()) { throw InvalidProtocolBufferException ("Expected size ${expectedSize} got ${tmp.getSize()}") }
readSize += tmp.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(tmp.getSize())
phones.add(tmp.build())
}
}
@@ -249,6 +265,21 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
while(parseFieldFrom(input)) {}
return this
}
fun getSize(): Int {
var size = 0
size += WireFormat.getStringSize(1, name)
size += WireFormat.getInt32Size(2, id)
size += WireFormat.getStringSize(3, email)
run {
var arraySize = 0
for (item in phones) {
arraySize += item.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize())
}
size += arraySize + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(arraySize)
}
size += WireFormat.getBytesSize(5, someBytes)
return size
}
}
@@ -257,12 +288,27 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
id = other.id
email = other.email
phones.addAll(other.phones)
someBytes?.plus(other.someBytes ?: ByteArray(0))
someBytes.plus(other.someBytes)
}
fun mergeFrom (input: CodedInputStream) {
val builder = BuilderPerson()
mergeWith(builder.parseFrom(input).build())}
fun getSize(): Int {
var size = 0
size += WireFormat.getStringSize(1, name)
size += WireFormat.getInt32Size(2, id)
size += WireFormat.getStringSize(3, email)
run {
var arraySize = 0
for (item in phones) {
arraySize += item.getSize() + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize())
}
size += arraySize + WireFormat.getTagSize(4, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(arraySize)
}
size += WireFormat.getBytesSize(5, someBytes)
return size
}
}
@@ -276,15 +322,21 @@ class AddressBook private constructor (people: MutableList <Person> = mutableLi
}
fun writeTo (output: CodedOutputStream): Unit {
writeToNoTag(output)
}
fun writeToNoTag (output: CodedOutputStream): Unit {
if (people.size > 0) {
output.writeTag(1, WireType.LENGTH_DELIMITED)
output.writeInt32NoTag(people.size)
var arrayByteSize = 0
run {
var arraySize = 0
for (item in people) {
arraySize += item.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize())
}
arrayByteSize += arraySize
}
output.writeInt32NoTag(arrayByteSize)
for (item in people) {
item.writeToNoTag(output)
output.writeTag(1, WireType.LENGTH_DELIMITED)
output.writeInt32NoTag(item.getSize())
item.writeTo(output)
}
}
}
@@ -317,15 +369,16 @@ class AddressBook private constructor (people: MutableList <Person> = mutableLi
}
fun readFrom (input: CodedInputStream): BuilderAddressBook {
return readFromNoTag(input)
}
fun readFromNoTag (input: CodedInputStream): BuilderAddressBook {
val tag = input.readTag(1, WireType.LENGTH_DELIMITED)
val listSize = input.readInt32NoTag()
for (i in 1..listSize) {
val expectedSize = input.readInt32NoTag()
var readSize = 0
while(readSize != expectedSize) {
val tmp: Person.BuilderPerson = Person.BuilderPerson()
tmp.readFromNoTag(input)
input.readTag(1, WireType.LENGTH_DELIMITED)
val expectedSize = input.readInt32NoTag()
tmp.readFrom(input)
if (expectedSize != tmp.getSize()) { throw InvalidProtocolBufferException ("Expected size ${expectedSize} got ${tmp.getSize()}") }
readSize += tmp.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(tmp.getSize())
people.add(tmp.build())
}
return this
@@ -343,10 +396,15 @@ class AddressBook private constructor (people: MutableList <Person> = mutableLi
val wireType = WireFormat.getTagWireType(tag)
when(fieldNumber) {
1 -> {
val listSize = input.readInt32NoTag()
for (i in 1..listSize) {
val expectedSize = input.readInt32NoTag()
var readSize = 0
while(readSize != expectedSize) {
val tmp: Person.BuilderPerson = Person.BuilderPerson()
tmp.readFromNoTag(input)
input.readTag(1, WireType.LENGTH_DELIMITED)
val expectedSize = input.readInt32NoTag()
tmp.readFrom(input)
if (expectedSize != tmp.getSize()) { throw InvalidProtocolBufferException ("Expected size ${expectedSize} got ${tmp.getSize()}") }
readSize += tmp.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(tmp.getSize())
people.add(tmp.build())
}
}
@@ -356,6 +414,17 @@ class AddressBook private constructor (people: MutableList <Person> = mutableLi
while(parseFieldFrom(input)) {}
return this
}
fun getSize(): Int {
var size = 0
run {
var arraySize = 0
for (item in people) {
arraySize += item.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize())
}
size += arraySize + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(arraySize)
}
return size
}
}
@@ -366,6 +435,17 @@ class AddressBook private constructor (people: MutableList <Person> = mutableLi
fun mergeFrom (input: CodedInputStream) {
val builder = BuilderAddressBook()
mergeWith(builder.parseFrom(input).build())}
fun getSize(): Int {
var size = 0
run {
var arraySize = 0
for (item in people) {
arraySize += item.getSize() + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(item.getSize())
}
size += arraySize + WireFormat.getTagSize(1, WireType.LENGTH_DELIMITED) + WireFormat.getVarint32Size(arraySize)
}
return size
}
}
+1 -1
View File
@@ -29,7 +29,7 @@ fun testMessageSerialization() {
.build()
)
.build() // don't forget to call build() to produce message
msg.writeToNoTag(outs)
msg.writeTo(outs)
// Now let's use output stream as input to read our message from it!
var ins = CodedInputStream(ByteArrayInputStream(s.toByteArray()))
+97
View File
@@ -4,8 +4,14 @@
object WireFormat {
// couple of constants for magic numbers
val TAG_TYPE_BITS: Int = 3
val TAG_TYPE_MASK: Int = (1 shl TAG_TYPE_BITS) - 1
val VARINT_INFO_BITS_COUNT: Int = 7
val VARINT_INFO_BITS_MASK: Int = 0b01111111 // mask for separating lowest 7 bits, where actual information stored
val VARINT_UTIL_BIT_MASK: Int = 0b10000000 // mask for separating highest bit, that indicates next byte presence
val FIXED_32_BYTE_SIZE: Int = 4
val FIXED_64_BYTE_SIZE: Int = 8
fun getTagWireType(tag: Int): WireType {
return WireType from (tag and TAG_TYPE_MASK).toByte()
@@ -14,4 +20,95 @@ object WireFormat {
fun getTagFieldNumber(tag: Int): Int {
return tag ushr TAG_TYPE_BITS
}
// TODO: refactor casts into function overloading as soon as translator will support it
fun getTagSize(fieldNumber: Int, wireType: WireType): Int {
return getVarint32Size((fieldNumber shl 3) or wireType.ordinal)
}
fun getVarint32Size(value: Int): Int {
var curValue = value
var size = 0
while (curValue != 0) {
size += 1
curValue = value ushr VARINT_INFO_BITS_COUNT
}
return size
}
fun getVarint64Size(value: Long): Int {
var curValue = value
var size = 0
while (curValue != 0L) {
size += 1
curValue = value ushr VARINT_INFO_BITS_COUNT
}
return size
}
fun getZigZag32Size(value: Int): Int {
return getVarint32Size((value shl 1) xor (value shr 31))
}
fun getZigZag64Size(value: Long): Int {
return getVarint64Size((value shl 1) xor (value shr 63))
}
fun getInt32Size(fieldNumber: Int, value: Int): Int {
return getTagSize(fieldNumber, WireType.VARINT) + getVarint32Size(value)
}
fun getUInt32Size(fieldNumber: Int, value: Int): Int {
return getInt32Size(fieldNumber, value)
}
fun getInt64Size(fieldNumber: Int, value: Long): Int {
return getTagSize(fieldNumber, WireType.VARINT) + getVarint64Size(value)
}
fun getUInt64Size(fieldNumber: Int, value: Long): Int {
return getInt64Size(fieldNumber, value)
}
fun getBoolSize(fieldNumber: Int, value: Boolean): Int {
val intValue = if (value) 1 else 0
return getInt32Size(fieldNumber, intValue)
}
fun getEnumSize(fieldNumber: Int, value: Int): Int {
return getInt32Size(fieldNumber, value)
}
fun getSInt32Size(fieldNumber: Int, value: Int): Int {
return getTagSize(fieldNumber, WireType.VARINT) + getZigZag32Size(value)
}
fun getSInt64Size(fieldNumber: Int, value: Long): Int {
return getTagSize(fieldNumber, WireType.VARINT) + getZigZag64Size(value)
}
fun getFixed32Size(fieldNumber: Int, value: Long): Int {
return getTagSize(fieldNumber, WireType.FIX_32) + FIXED_32_BYTE_SIZE
}
fun getFixed64Size(fieldNumber: Int, value: Long): Int {
return getTagSize(fieldNumber, WireType.FIX_64) + FIXED_64_BYTE_SIZE
}
fun getDoubleSize(fieldNumber: Int, value: Double): Int {
return getTagSize(fieldNumber, WireType.FIX_32) + FIXED_32_BYTE_SIZE
}
fun getFloatSize(fieldNumber: Int, value: Float): Int {
return getTagSize(fieldNumber, WireType.FIX_64) + FIXED_64_BYTE_SIZE
}
fun getStringSize(fieldNumber: Int, value: String): Int {
val encodedStringSize = value.toByteArray(Charsets.UTF_8).size //TODO: not sure if it's the best way to do it
return encodedStringSize + getTagSize(fieldNumber, WireType.LENGTH_DELIMITED) + getVarint32Size(encodedStringSize)
}
fun getBytesSize(fieldNumber: Int, value: ByteArray): Int {
return value.size + getTagSize(fieldNumber, WireType.LENGTH_DELIMITED) + getVarint32Size(value.size)
}
}