Protobuf: changed all arrays in generated code into primitive arrays

This commit is contained in:
dsavvinov
2016-08-08 18:02:53 +03:00
parent 10fb92f9fa
commit 8c96f21515
5 changed files with 94 additions and 191 deletions
@@ -149,7 +149,7 @@ void ClassGenerator::generateMergeMethods(io::Printer *printer) const {
// concatenate repeated fields
if (properties[i]->getProtoLabel() == FieldDescriptor::LABEL_REPEATED) {
printer->Print(vars, "$fieldName$.addAll(other.$fieldName$)\n");
printer->Print(vars, "$fieldName$ = $fieldName$.plus((other.$fieldName$))\n");
}
// Bytes type is handled separately
@@ -17,7 +17,7 @@ namespace kotlin {
string FieldGenerator::getInitValue() const {
if (getProtoLabel() == FieldDescriptor::LABEL_REPEATED) {
return "mutableListOf()";
return getFullType() + "(0)";
}
if (getProtoType() == FieldDescriptor::TYPE_MESSAGE) {
return getBuilderFullType() + "().build()";
@@ -58,12 +58,13 @@ FieldGenerator::FieldGenerator(FieldDescriptor const * descriptor, ClassGenerato
, protoLabel(descriptor->label())
{ }
void FieldGenerator::generateSerializationForPacked(io::Printer *printer, bool isRead, bool noTag) const {
void FieldGenerator::generateSerializationForPacked(io::Printer *printer, bool isRead, bool noTag, bool isField) const {
map <string, string> vars;
vars["fieldNumber"] = std::to_string(getFieldNumber());
vars["builderType"] = getUnderlyingTypeGenerator().getFullType();
vars["underlyingType"] = getUnderlyingTypeGenerator().getFullType();
vars["initValue"] = getUnderlyingTypeGenerator().getInitValue();
vars["fieldName"] = simpleName;
vars["arrayType"] = getFullType();
bool isPrimitive = descriptor->type() != FieldDescriptor::TYPE_BYTES &&
descriptor->type() != FieldDescriptor::TYPE_MESSAGE &&
@@ -74,10 +75,12 @@ void FieldGenerator::generateSerializationForPacked(io::Printer *printer, bool i
if (!noTag) {
printer->Print(vars, "val tag = input.readTag($fieldNumber$, WireType.LENGTH_DELIMITED)\n");
}
printer->Print(vars, "val expectedSize = input.readInt32NoTag()\n");
printer->Print("var readSize = 0\n");
printer->Print(vars, "while(readSize != expectedSize) {\n");
printer->Indent();
printer->Print(vars, "val expectedByteSize = input.readInt32NoTag()\n");
/* Now we want to compute element size of array. For this, we are estimating byte size of a single element,
* and then divide byte size of whole array by byte size of single element.
*/
printer->Print(vars, "var singleElemSize = 0\n");
/* hack: copy current FieldGenerator and change label to OPTIONAL. Also change name to
name of iterator in for-loop.
@@ -89,40 +92,44 @@ void FieldGenerator::generateSerializationForPacked(io::Printer *printer, bool i
(then writing CodedOutputStream.writeMessage will be possible).
*/
FieldGenerator singleFieldGen = getUnderlyingTypeGenerator();
singleFieldGen.simpleName = "singleElemSize";
singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ true, /* isField = */ false);
/* 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().
This is incorrect because array has old size, while 'i' iterates over new size, which can lead
to ArrayOutOfIndex errors.
*/
printer->Print(vars, "var tmp: $builderType$ = $initValue$\n");
printer->Print(vars, "val arraySize = expectedByteSize / singleElemSize\n");
// Allocate new array of estimated size
printer->Print(vars, "val newArray = $arrayType$(arraySize)\n");
printer->Print("var i = 0\n");
printer->Print(vars, "while(i < arraySize) {\n");
printer->Indent();
printer->Print(vars, "var tmp: $underlyingType$ = $initValue$\n");
singleFieldGen.simpleName = "tmp";
singleFieldGen.protoLabel = FieldDescriptor::LABEL_OPTIONAL;
singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ true);
singleFieldGen.generateSizeEstimationCode(printer, /* varName = */
"readSize"); // add size of current element to total size //TODO: think is it's ok that we estimate size with tag here
singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ true, /* isField = */ false);
printer->Print(vars, "$fieldName$.add(tmp)\n");
printer->Print(vars, "$fieldName$[i] = tmp\n");
printer->Print("i += 1\n");
printer->Outdent();
printer->Print("}\n");
}
else {
/**
* Protobuf format:
* - Check if size of array is > 0, because empty repeated fields shouldn't appear in message
* - Write tag explicitly
* - Write length as int32 (note that tag shouldn't be added)
* - Write all repeated elements via recursive call (for primitive types without tags)
*/
* Protobuf format:
* - Check if size of array is > 0, because empty repeated fields shouldn't appear in message
* - Write tag explicitly
* - Write length as int32 (note that tag shouldn't be added)
* - Write all repeated elements via recursive call (for primitive types without tags)
*/
// tag
printer->Print(vars, "output.writeTag($fieldNumber$, WireType.LENGTH_DELIMITED)\n");
// length
printer->Print(vars, "var arrayByteSize = 0\n");
generateSizeEstimationCode(printer, "arrayByteSize", /* noTag = */ true);
generateSizeEstimationCode(printer, "arrayByteSize", /* noTag = */ true, /* isField = */ false);
printer->Print(vars, "output.writeInt32NoTag(arrayByteSize)\n");
// all elements
@@ -134,75 +141,14 @@ void FieldGenerator::generateSerializationForPacked(io::Printer *printer, bool i
singleFieldGen.simpleName = "item";
singleFieldGen.protoLabel = FieldDescriptor::LABEL_OPTIONAL;
singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ isPrimitive);
singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ isPrimitive, /* isField = */ false);
printer->Outdent(); // for-loop
printer->Print("}\n");
}
}
void FieldGenerator::generateSerializationForRepeated(io::Printer *printer, bool isRead, bool noTag) const {
map <string, string> vars;
vars["fieldNumber"] = std::to_string(getFieldNumber());
vars["builderType"] = getUnderlyingTypeGenerator().getFullType();
vars["initValue"] = getUnderlyingTypeGenerator().getInitValue();
vars["fieldName"] = simpleName;
if (isRead) {
printer->Print("var readSize = 0\n");
/* hack: copy current FieldGenerator and change label to OPTIONAL. Also change name to
name of iterator in for-loop.
This will allow to re-use this function for generating serialization code for elements of array.
More importantly, this will care about nested types too.
Efficiently, it inlines serialization code for all underlying types.
This hack isn't necessary from the architectural point of view and could be safely
removed as soon as target code will support inheritance and interfaces.
(then writing CodedOutputStream.writeMessage will be possible).
*/
FieldGenerator singleFieldGen = getUnderlyingTypeGenerator();
/* 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().
This is incorrect because array has old size, while 'i' iterates over new size, which can lead
to ArrayOutOfIndex errors.
*/
printer->Print(vars, "var tmp: $builderType$ = $initValue$\n");
singleFieldGen.simpleName = "tmp";
singleFieldGen.protoLabel = FieldDescriptor::LABEL_OPTIONAL;
singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ true);
singleFieldGen.generateSizeEstimationCode(printer, /* varName = */
"readSize", /* noTag = */ true); // add size of current element to total size. Note that
printer->Print(vars, "$fieldName$.add(tmp)\n");
}
else {
/**
* Protobuf format:
* - Check if size of array is > 0, because empty repeated fields shouldn't appear in message
* - Write tag explicitly
* - Write length as int32 (note that tag shouldn't be added)
* - Write all repeated elements via recursive call (for primitive types without tags)
*/
// all elements
printer->Print(vars, "for (item in $fieldName$) {\n");
printer->Indent();
// hack: see above
FieldGenerator singleFieldGen = FieldGenerator(descriptor, enclosingClass, nameResolver);
singleFieldGen.simpleName = "item";
singleFieldGen.protoLabel = FieldDescriptor::LABEL_OPTIONAL;
singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ true);
printer->Outdent(); // for-loop
printer->Print("}\n");
}
}
void FieldGenerator::generateSerializationForEnums(io::Printer * printer, bool isRead, bool noTag) const {
void FieldGenerator::generateSerializationForEnums(io::Printer * printer, bool isRead, bool noTag, bool isField) const {
map <string, string> vars;
vars["converter"] = getEnumFromIntConverter();
@@ -229,7 +175,7 @@ void FieldGenerator::generateSerializationForEnums(io::Printer * printer, bool i
}
}
void FieldGenerator::generateSerializationForMessages(io::Printer * printer, bool isRead, bool noTag) const {
void FieldGenerator::generateSerializationForMessages(io::Printer * printer, bool isRead, bool noTag, bool isField) const {
map <string, string> vars;
vars["fieldNumber"] = std::to_string(getFieldNumber());
vars["dollar"] = "$";
@@ -274,7 +220,7 @@ void FieldGenerator::generateSerializationForMessages(io::Printer * printer, boo
}
}
void FieldGenerator::generateSerializationForPrimitives(io::Printer * printer, bool isRead, bool noTag) const {
void FieldGenerator::generateSerializationForPrimitives(io::Printer * printer, bool isRead, bool noTag, bool isField) const {
map <string, string> vars;
vars["fieldName"] = simpleName;
vars["suffix"] = getKotlinFunctionSuffix();
@@ -297,7 +243,7 @@ void FieldGenerator::generateSerializationForPrimitives(io::Printer * printer, b
}
}
void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead, bool noTag) const {
void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead, bool noTag, bool isField) const {
map <string, string> vars;
vars["fieldName"] = simpleName;
vars["initValue"] = getInitValue();
@@ -307,60 +253,50 @@ void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead
* Note that it should be first check because of Google's FieldDescriptor structure */
if (getProtoLabel() == FieldDescriptor::LABEL_REPEATED) {
// we shouldn't write fields with default values when writing
if (!isRead) {
if (!isRead && isField) {
printer->Print(vars, "if ($fieldName$.size > 0) {\n");
printer->Indent();
}
bool isPrimitive = descriptor->type() != FieldDescriptor::TYPE_BYTES &&
descriptor->type() != FieldDescriptor::TYPE_MESSAGE &&
descriptor->type() != FieldDescriptor::TYPE_STRING &&
descriptor->type() != FieldDescriptor::TYPE_ENUM;
descriptor->type() != FieldDescriptor::TYPE_STRING;
if (isPrimitive) {
generateSerializationForPacked(printer, isRead, noTag);
generateSerializationForPacked(printer, isRead, noTag, isField);
}
else {
generateSerializationForRepeated(printer, isRead, noTag);
throw UnreachableStateException("Arrays of non-primitive types are not supported");
}
}
/* Then check is current field is enum. We have to handle it separately too, because
* we have to pass enums as Int's to CodedStreams as per protobuf-format */
else if (descriptor->type() == FieldDescriptor::TYPE_ENUM) {
// we shouldn't write fields with default values when writing
if (!isRead) {
printer->Print(vars, "if ($fieldName$ != $initValue$) {\n");
printer->Indent();
}
generateSerializationForEnums(printer, isRead, noTag);
}
/* Then check for nested messages. Here we re-use writeTo method, that should be defined in
* 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. */
else if (descriptor->type() == FieldDescriptor::TYPE_MESSAGE) {
// we shouldn't write fields with default values when writing
if (!isRead) {
printer->Print(vars, "if ($fieldName$ != $initValue$) {\n");
printer->Indent();
}
generateSerializationForMessages(printer, isRead, noTag);
}
/* Finally, serialize trivial cases */
else {
// we shouldn't write fields with default values when writing
if (!isRead) {
// we shouldn't write fair fields with default values
if (!isRead && isField) {
printer->Print(vars, "if ($fieldName$ != $initValue$) {\n");
printer->Indent();
}
generateSerializationForPrimitives(printer, isRead, noTag);
/* Then check is current field is enum. We have to handle it separately too, because
* we have to pass enums as Int's to CodedStreams as per protobuf-format */
if (descriptor->type() == FieldDescriptor::TYPE_ENUM) {
generateSerializationForEnums(printer, isRead, noTag, isField);
}
/* Then check for nested messages. Here we re-use writeTo method, that should be defined in
* 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. */
else if (descriptor->type() == FieldDescriptor::TYPE_MESSAGE) {
generateSerializationForMessages(printer, isRead, noTag, isField);
}
/* Finally, serialize trivial cases */
else {
generateSerializationForPrimitives(printer, isRead, noTag, isField);
}
}
if (!isRead) {
if (!isRead && isField) {
printer->Outdent();
printer->Print("}\n");
}
@@ -392,45 +328,20 @@ void FieldGenerator::generateRepeatedMethods(io::Printer * printer, bool isBuild
// generate indexed setter for builders
if (isBuilder) {
printer->Print(vars, "fun set$elementType$(index: Int, $arg$: $elementType$): $builderName$ {\n");
printer->Print(vars, "fun set$fieldName$ByIndex(index: Int, $arg$: $elementType$): $builderName$ {\n");
printer->Indent();
printer->Print(vars, "$fieldName$[index] = $arg$\n");
printer->Print(vars, "return this\n");
printer->Outdent();
printer->Print("}\n");
}
if (isBuilder) {
// generate single-add for builders
printer->Print(vars, "fun add$elementType$($arg$: $elementType$): $builderName$ {\n");
printer->Indent();
printer->Print(vars, "$fieldName$.add($arg$)\n"
"return this\n");
printer->Outdent();
printer->Print(vars, "}\n");
// generate addAll for builders
printer->Print(vars, "fun addAll$elementType$($arg$: Iterable<$elementType$>): $builderName$ {\n");
printer->Indent();
printer->Print(vars, "for (item in $arg$) {\n");
printer->Indent();
printer->Print(vars, "$fieldName$.add(item)\n");
printer->Outdent(); // for-loop
printer->Print("}\n");
printer->Print("return this\n");
printer->Outdent(); // function body
printer->Print("}\n");
}
}
string FieldGenerator::getKotlinFunctionSuffix() const {
return name_resolving::protobufTypeToKotlinFunctionSuffix(descriptor->type());
}
void FieldGenerator::generateSizeEstimationCode(io::Printer *printer, string varName, bool noTag) const {
void FieldGenerator::generateSizeEstimationCode(io::Printer *printer, string varName, bool noTag, bool isField) const {
map<string, string> vars;
vars["varName"] = varName;
vars["fieldName"] = simpleName;
@@ -459,7 +370,7 @@ void FieldGenerator::generateSizeEstimationCode(io::Printer *printer, string var
FieldGenerator singleFieldGen = FieldGenerator(descriptor, enclosingClass, nameResolver);
singleFieldGen.protoLabel = FieldDescriptor::LABEL_OPTIONAL;
singleFieldGen.simpleName = "item";
singleFieldGen.generateSizeEstimationCode(printer, "arraySize", noTag);
singleFieldGen.generateSizeEstimationCode(printer, "arraySize", noTag, /* isField = */ false);
printer->Outdent(); // for-loop
printer->Print("}\n");
@@ -477,42 +388,38 @@ void FieldGenerator::generateSizeEstimationCode(io::Printer *printer, string var
return;
}
// If we are generating code for fair field (not for dummy), then we should check for default value
// Note that we can't lift that check up, because repeated fields should be treated separately
if (isField) {
printer->Print(vars, "if ($fieldName$ != $initValue$) {\n");
printer->Indent();
}
// 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 (getProtoType() == FieldDescriptor::TYPE_MESSAGE) {
printer->Print(vars, "if ($fieldName$ != $initValue$) {\n");
printer->Indent();
vars["maybeNoTag"] = noTag ? "NoTag" : "";
vars["maybeFieldNumber"] = noTag ? "" : std::to_string(getFieldNumber());
printer->Print(vars, "$varName$ += $fieldName$.getSize$maybeNoTag$($maybeFieldNumber$)"
"\n");
printer->Outdent(); // if-clause
printer->Print("}\n");
return;
"\n");
}
// Next, process enums as they should be casted to ints manually
if (getProtoType() == FieldDescriptor::TYPE_ENUM) {
printer->Print(vars, "if ($fieldName$ != $initValue$) {\n");
printer->Indent();
else if (getProtoType() == FieldDescriptor::TYPE_ENUM) {
printer->Print(vars, "$varName$ += WireFormat.getEnumSize($fieldNumber$, $fieldName$.ord)\n");
printer->Outdent(); // if-clause
printer->Print("}\n");
return;
}
// Finally, get size of all primitive types trivially via call to WireFormat in runtime
vars["kotlinSuffix"] = getKotlinFunctionSuffix();
printer->Print(vars, "if ($fieldName$ != $initValue$) {\n");
printer->Indent();
else {
vars["kotlinSuffix"] = getKotlinFunctionSuffix();
printer->Print(vars, "$varName$ += WireFormat.get$kotlinSuffix$Size($fieldNumber$, $fieldName$)\n");
}
printer->Print(vars, "$varName$ += WireFormat.get$kotlinSuffix$Size($fieldNumber$, $fieldName$)\n");
printer->Outdent(); // if-clause
printer->Print("}\n");
return;
if (isField) {
printer->Outdent(); // if-clause
printer->Print("}\n");
}
}
FieldDescriptor::Label FieldGenerator::getProtoLabel() const {
@@ -543,7 +450,7 @@ string FieldGenerator::getSimpleType() const {
string FieldGenerator::getFullType() const {
if (getProtoLabel() == FieldDescriptor::LABEL_REPEATED) {
return "MutableList <" + getUnderlyingTypeGenerator().getFullType() + ">";
return getUnderlyingTypeGenerator().getFullType() + "Array";
}
if (getProtoType() == FieldDescriptor::TYPE_MESSAGE ||
getProtoType() == FieldDescriptor::TYPE_ENUM) {
@@ -27,11 +27,11 @@ private:
void generateSetter(io::Printer * printer) const;
void generateRepeatedMethods(io::Printer * printer, bool isBuilder) const;
void generateSerializationForPacked (io::Printer * printer, bool isRead, bool noTag) const;
void generateSerializationForRepeated (io::Printer * printer, bool isRead, bool noTag) const;
void generateSerializationForEnums (io::Printer * printer, bool isRead, bool noTag) const;
void generateSerializationForMessages (io::Printer * printer, bool isRead, bool noTag) const;
void generateSerializationForPrimitives (io::Printer * printer, bool isRead, bool noTag) const;
void generateSerializationForPacked (io::Printer * printer, bool isRead, bool noTag, bool isField) const;
void generateSerializationForRepeated (io::Printer * printer, bool isRead, bool noTag, bool isField) const;
void generateSerializationForEnums (io::Printer * printer, bool isRead, bool noTag, bool isField) const;
void generateSerializationForMessages (io::Printer * printer, bool isRead, bool noTag, bool isField) const;
void generateSerializationForPrimitives (io::Printer * printer, bool isRead, bool noTag, bool isField) const;
public:
ClassGenerator const * enclosingClass; // class, in which that field is defined
NameResolver * nameResolver;
@@ -84,8 +84,8 @@ public:
string getWireType() const;
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;
void generateSerializationCode (io::Printer * printer, bool isRead = false, bool noTag = false, bool isField = true) const;
void generateSizeEstimationCode(io::Printer * printer, string varName, bool noTag = false, bool isField = true) const;
FieldGenerator(FieldDescriptor const * descriptor, ClassGenerator const * enclosingClass, NameResolver * nameResolver);
};
@@ -5,12 +5,8 @@ option java_package = "proto.car";
option java_outer_classname = "Route";
message RouteRequest {
repeated WayPoint way_points = 1;
message WayPoint {
int32 distance = 2;
int32 angle_delta = 3;
}
repeated int32 dinstances = 1;
repeated int32 angle_deltas = 2;
}
message RouteResponse {