Compiler: added parsers
This commit is contained in:
+114
-11
@@ -12,6 +12,8 @@ namespace protobuf {
|
||||
namespace compiler {
|
||||
namespace kotlin {
|
||||
|
||||
class FieldGenerator; // declared in "kotlin_file_generator.h"
|
||||
|
||||
void ClassGenerator::generateCode(io::Printer *printer, bool isBuilder) const {
|
||||
generateHeader(printer, isBuilder);
|
||||
printer->Indent();
|
||||
@@ -22,7 +24,7 @@ void ClassGenerator::generateCode(io::Printer *printer, bool isBuilder) const {
|
||||
* Also note that fields should be declared before init section.
|
||||
*/
|
||||
for (FieldGenerator *gen: properties) {
|
||||
gen->generateCode(printer, isBuilder, simpleName);
|
||||
gen->generateCode(printer, isBuilder);
|
||||
printer->Print("\n");
|
||||
}
|
||||
|
||||
@@ -54,13 +56,16 @@ void ClassGenerator::generateCode(io::Printer *printer, bool isBuilder) const {
|
||||
generateBuilder(printer);
|
||||
|
||||
printer->Print("\n");
|
||||
generateMergeFrom(printer);
|
||||
generateMergeMethods(printer);
|
||||
}
|
||||
|
||||
// build() and setters are only for builders
|
||||
if (isBuilder) {
|
||||
printer->Print("\n");
|
||||
generateBuildMethod(printer);
|
||||
|
||||
printer->Print("\n");
|
||||
generateParseMethods(printer);
|
||||
}
|
||||
|
||||
printer->Outdent();
|
||||
@@ -70,11 +75,12 @@ void ClassGenerator::generateCode(io::Printer *printer, bool isBuilder) const {
|
||||
ClassGenerator::ClassGenerator(Descriptor const *descriptor)
|
||||
: descriptor(descriptor) {
|
||||
simpleName = descriptor->name();
|
||||
builderName = "Builder" + simpleName;
|
||||
|
||||
int field_count = descriptor->field_count();
|
||||
for (int i = 0; i < field_count; ++i) {
|
||||
FieldDescriptor const * fieldDescriptor = descriptor->field(i);
|
||||
properties.push_back(new FieldGenerator(fieldDescriptor));
|
||||
properties.push_back(new FieldGenerator(fieldDescriptor, /* enclosingClass = */ this));
|
||||
}
|
||||
|
||||
int nested_types_count = descriptor->nested_type_count();
|
||||
@@ -111,24 +117,58 @@ void ClassGenerator::generateBuilder(io::Printer * printer) const {
|
||||
generateCode(printer, /* isBuilder = */ true);
|
||||
}
|
||||
|
||||
void ClassGenerator::generateMergeFrom(io::Printer * printer) const {
|
||||
//TODO: Looks pretty dirty. Should reconsider process of generating readFrom, mergeFrom and writeTo.
|
||||
void ClassGenerator::generateMergeMethods(io::Printer *printer) const {
|
||||
map <string, string> vars;
|
||||
printer->Print(vars, "fun mergeFrom (input: CodedInputStream) {\n");
|
||||
|
||||
// mergeWith(other: Message)
|
||||
printer->Print("\n");
|
||||
vars["className"] = simpleName;
|
||||
printer->Print(vars, "fun mergeWith (other: $className$) {\n");
|
||||
printer->Indent();
|
||||
|
||||
for (int i = 0; i < properties.size(); ++i) {
|
||||
properties[i]->generateSerializationCode(printer, /* isRead = */ true);
|
||||
vars["fieldName"] = properties[i]->simpleName;
|
||||
|
||||
// concatenate repeated fields
|
||||
if (properties[i]->modifier == FieldDescriptor::LABEL_REPEATED) {
|
||||
printer->Print(vars, "$fieldName$.addAll(other.$fieldName$)\n");
|
||||
}
|
||||
|
||||
// Bytes type is handled separately
|
||||
else if (properties[i]->protoType == FieldDescriptor::TYPE_BYTES) {
|
||||
vars["initValue"] = properties[i]->getInitValue();
|
||||
printer->Print(vars, "$fieldName$?.plus(other.$fieldName$ ?: $initValue$)\n");
|
||||
}
|
||||
|
||||
// for all other cases just take other's field
|
||||
else {
|
||||
printer->Print(vars, "$fieldName$ = other.$fieldName$\n");
|
||||
}
|
||||
}
|
||||
|
||||
printer->Outdent();
|
||||
printer->Print("}\n");
|
||||
|
||||
|
||||
// mergeFrom(input: CodedInputStream)
|
||||
printer->Print("\n");
|
||||
printer->Print(vars, "fun mergeFrom (input: CodedInputStream) {\n");
|
||||
printer->Indent();
|
||||
|
||||
vars["builderName"] = builderName;
|
||||
printer->Print(vars, "val builder = $builderName$()\n");
|
||||
printer->Print("mergeWith(builder.parseFrom(input).build())");
|
||||
|
||||
printer->Outdent();
|
||||
printer->Print("}\n");
|
||||
}
|
||||
|
||||
void ClassGenerator::generateSerializers(io::Printer * printer, bool isRead) const {
|
||||
// readFrom(input: CodedInputStream) OR
|
||||
// writeTo(output: CodedOutputStream)
|
||||
map <string, string> vars;
|
||||
vars["funName"]= isRead ? "readFrom" : "writeTo";
|
||||
vars["returnType"] = isRead ? "Builder" + simpleName : "Unit";
|
||||
vars["returnType"] = isRead ? builderName : "Unit";
|
||||
vars["stream"] = isRead ? "CodedInputStream" : "CodedOutputStream";
|
||||
vars["arg"] = isRead ? "input" : "output";
|
||||
vars["maybeSeparator"] = isRead ? "" : ", ";
|
||||
@@ -151,7 +191,7 @@ void ClassGenerator::generateSerializersNoTag(io::Printer *printer, bool isRead)
|
||||
vars["funName"]= isRead ? "readFromNoTag" : "writeToNoTag";
|
||||
vars["stream"] = isRead ? "CodedInputStream" : "CodedOutputStream";
|
||||
vars["arg"] = isRead ? "input" : "output";
|
||||
vars["returnType"] = isRead ? "Builder" + simpleName : "Unit";
|
||||
vars["returnType"] = isRead ? builderName : "Unit";
|
||||
vars["maybeReturn"] = isRead ? "return this\n" : "";
|
||||
|
||||
// generate function header
|
||||
@@ -176,14 +216,14 @@ void ClassGenerator::generateHeader(io::Printer * printer, bool isBuilder) const
|
||||
// build list of arguments like 'field1: Type1, field2: Type2, ... '
|
||||
string argumentList = "";
|
||||
for (int i = 0; i < properties.size(); ++i) {
|
||||
argumentList += properties[i]->simpleName + ": " + properties[i]->fullType + " = " + properties[i]->initValue;
|
||||
argumentList += properties[i]->simpleName + ": " + properties[i]->fullType + " = " + properties[i]->getInitValue();
|
||||
if (i + 1 != properties.size()) {
|
||||
argumentList += ", ";
|
||||
}
|
||||
}
|
||||
|
||||
map<string, string> vars;
|
||||
vars["name"] = (isBuilder? "Builder" : "") + simpleName;
|
||||
vars["name"] = isBuilder? builderName : simpleName;
|
||||
vars["argumentList"] = argumentList;
|
||||
vars["maybePrivate"] = isBuilder? "" : " private";
|
||||
printer->Print(vars,
|
||||
@@ -230,6 +270,69 @@ void ClassGenerator::generateInitSection(io::Printer * printer) const {
|
||||
printer->Print("}\n");
|
||||
}
|
||||
|
||||
void ClassGenerator::generateParseMethods(io::Printer *printer) const {
|
||||
// parseFieldFrom(input: CodedInputStream): Boolean
|
||||
map <string, string> vars;
|
||||
vars["builderName"] = builderName;
|
||||
|
||||
printer->Print("fun parseFieldFrom(input: CodedInputStream): Boolean {\n");
|
||||
printer->Indent();
|
||||
|
||||
// messages are not required to end with 0-tag, therefore parsing method should check for EOF
|
||||
printer->Print("if (input.isAtEnd()) { return false }\n");
|
||||
|
||||
// read tag and check if some field will follow (0-tag inidcates end of message)
|
||||
printer->Print("val tag = input.readInt32NoTag()\n");
|
||||
printer->Print("if (tag == 0) { return false } \n");
|
||||
|
||||
// parse tag into field number and wire type
|
||||
printer->Print("val fieldNumber = WireFormat.getTagFieldNumber(tag)\n");
|
||||
printer->Print("val wireType = WireFormat.getTagWireType(tag)\n");
|
||||
|
||||
// 'when' to map fieldNumber into fieldName
|
||||
printer->Print("when(fieldNumber) {\n");
|
||||
printer->Indent();
|
||||
|
||||
for (int i = 0; i < properties.size(); ++i) {
|
||||
vars["fieldNumber"] = std::to_string(properties[i]->fieldNumber);
|
||||
vars["kotlinFunSuffix"] = properties[i]->getKotlinFunctionSuffix();
|
||||
printer->Print(vars, "$fieldNumber$ -> ");
|
||||
|
||||
// code for serialization arrays and messages consists of more than one line and needs enclosing brackets
|
||||
if (properties[i]->modifier == FieldDescriptor::LABEL_REPEATED
|
||||
|| properties[i]->protoType == FieldDescriptor::TYPE_MESSAGE) {
|
||||
printer->Print("{\n");
|
||||
printer->Indent();
|
||||
}
|
||||
|
||||
properties[i]->generateSerializationCode(printer, /* isRead = */ true, /* noTag = */ true);
|
||||
|
||||
if (properties[i]->modifier == FieldDescriptor::LABEL_REPEATED
|
||||
|| properties[i]->protoType == FieldDescriptor::TYPE_MESSAGE) {
|
||||
printer->Outdent();
|
||||
printer->Print("}\n");
|
||||
}
|
||||
}
|
||||
|
||||
printer->Outdent();
|
||||
printer->Print("}\n"); // when-clause
|
||||
|
||||
printer->Print("return true");
|
||||
printer->Outdent();
|
||||
printer->Print("}\n"); // parseFieldFrom body
|
||||
|
||||
// parseFrom(input: CodedInputStream)
|
||||
printer->Print(vars,
|
||||
"fun parseFrom(input: CodedInputStream): $builderName$ {\n");
|
||||
printer->Indent();
|
||||
printer->Print("while(parseFieldFrom(input)) {}\n");
|
||||
printer->Print("return this\n");
|
||||
|
||||
printer->Outdent();
|
||||
printer->Print("}\n");
|
||||
}
|
||||
|
||||
|
||||
const string ClassModifier::getName() const {
|
||||
string result = "";
|
||||
switch (type) {
|
||||
|
||||
+5
-2
@@ -16,6 +16,8 @@ namespace protobuf {
|
||||
namespace compiler {
|
||||
namespace kotlin {
|
||||
|
||||
class FieldGenerator; // declared in "kotlin_file_generator.h"
|
||||
|
||||
// wrapper for enum CLASS/INTERFACE with convenience method of getting name
|
||||
class ClassModifier {
|
||||
public:
|
||||
@@ -36,7 +38,7 @@ public:
|
||||
vector <FieldGenerator *> properties;
|
||||
vector <ClassGenerator *> classesDeclarations;
|
||||
vector <EnumGenerator *> enumsDeclaraions;
|
||||
|
||||
string builderName;
|
||||
ClassGenerator (Descriptor const * descriptor);
|
||||
~ClassGenerator ();
|
||||
|
||||
@@ -62,7 +64,8 @@ private:
|
||||
*/
|
||||
void generateSerializersNoTag(io::Printer *printer, bool isRead = false) const;
|
||||
void generateSerializers(io::Printer * printer, bool isRead = false) const;
|
||||
void generateMergeFrom(io::Printer * printer) const;
|
||||
void generateMergeMethods(io::Printer *printer) const;
|
||||
void generateParseMethods(io::Printer * printer) const;
|
||||
};
|
||||
|
||||
} // namespace kotlin
|
||||
|
||||
+83
-51
@@ -45,6 +45,7 @@ string FieldGenerator::protobufToKotlinField() const {
|
||||
/**
|
||||
* Simply maps protobuf field type to corresponding Kotlin.
|
||||
*/
|
||||
// TODO: refactor in separate static namespace
|
||||
string FieldGenerator::protobufToKotlinType() const {
|
||||
FieldDescriptor::Type type = descriptor->type();
|
||||
switch(type) {
|
||||
@@ -87,14 +88,8 @@ string FieldGenerator::protobufToKotlinType() const {
|
||||
}
|
||||
}
|
||||
|
||||
string FieldGenerator::getInitValue() const {
|
||||
if (descriptor->is_repeated())
|
||||
#ifndef KOTLIN_GENERATED_CODE_LANGUAGE_LEVEL_LOW
|
||||
return "mutableListOf()";
|
||||
#else
|
||||
return "arrayOf()";
|
||||
#endif
|
||||
|
||||
// TODO: refactor in separate static
|
||||
string FieldGenerator::protobufTypeToInitValue(FieldDescriptor const * descriptor) const {
|
||||
FieldDescriptor::Type type = descriptor->type();
|
||||
switch(type) {
|
||||
case FieldDescriptor::TYPE_BOOL:
|
||||
@@ -135,8 +130,18 @@ string FieldGenerator::getInitValue() const {
|
||||
return "0L"; // see notes for TYPE_FIXED64
|
||||
}
|
||||
}
|
||||
string FieldGenerator::getInitValue() const {
|
||||
if (descriptor->is_repeated())
|
||||
#ifndef KOTLIN_GENERATED_CODE_LANGUAGE_LEVEL_LOW
|
||||
return "mutableListOf()";
|
||||
#else
|
||||
return "arrayOf()";
|
||||
#endif
|
||||
|
||||
void FieldGenerator::generateCode(io::Printer *printer, bool isBuilder, string className) const {
|
||||
return protobufTypeToInitValue(descriptor);
|
||||
}
|
||||
|
||||
void FieldGenerator::generateCode(io::Printer *printer, bool isBuilder) const {
|
||||
map<string, string> vars;
|
||||
vars["name"] = simpleName;
|
||||
vars["field"] = protobufToKotlinField();
|
||||
@@ -149,30 +154,35 @@ void FieldGenerator::generateCode(io::Printer *printer, bool isBuilder, string c
|
||||
|
||||
// generate setter for builder
|
||||
if (isBuilder) {
|
||||
generateSetter(printer, /* builderName = */ "Builder" + className);
|
||||
generateSetter(printer);
|
||||
}
|
||||
|
||||
// generate additional methods for repeated fields
|
||||
if (modifier == FieldDescriptor::LABEL_REPEATED) {
|
||||
generateRepeatedMethods(printer, isBuilder, /* builderName = */ "Builder" + className);
|
||||
generateRepeatedMethods(printer, isBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
FieldGenerator::FieldGenerator(FieldDescriptor const * descriptor)
|
||||
FieldGenerator::FieldGenerator(FieldDescriptor const * descriptor, ClassGenerator const * enclosingClass)
|
||||
: descriptor(descriptor)
|
||||
, modifier(descriptor->label())
|
||||
, enclosingClass(enclosingClass)
|
||||
, simpleName(descriptor->name())
|
||||
, underlyingType(protobufToKotlinType())
|
||||
, fullType(protobufToKotlinField())
|
||||
, initValue(getInitValue())
|
||||
, protoType(descriptor->type())
|
||||
, fieldNumber(descriptor->number())
|
||||
{ }
|
||||
|
||||
// TODO: long, complicated and messy method. Refactor it ASAP
|
||||
void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead, bool noTag) const {
|
||||
map <string, string> vars;
|
||||
vars["type"] = protobufTypeToKotlinFunctionSuffix(descriptor->type());
|
||||
vars["fieldNumber"] = std::to_string(descriptor->number());
|
||||
vars["type"] = protobufTypeToKotlinFunctionSuffix(descriptor->type()) + (noTag ? "NoTag" : "");
|
||||
vars["fieldNumber"] = std::to_string(fieldNumber);
|
||||
vars["maybeFieldNumber"] = noTag ? "" : std::to_string(fieldNumber);
|
||||
vars["fieldName"] = simpleName;
|
||||
vars["arg"] = isRead ? "input" : "output";
|
||||
vars["maybeComma"] = ", ";
|
||||
|
||||
/**
|
||||
* First of all, try to generate syntax for repeated fields because it's separate case.
|
||||
@@ -180,56 +190,74 @@ void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead
|
||||
* - 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
|
||||
* - Write all repeated elements via recursive call (again, without tags)
|
||||
*/
|
||||
if (modifier == FieldDescriptor::LABEL_REPEATED) {
|
||||
printer->Print(vars, "if ($fieldName$.size > 0) {\n");
|
||||
printer->Indent();
|
||||
|
||||
// tag
|
||||
if (isRead) {
|
||||
//TODO: dirty stub here! Normally, reading from input should be delegated to Parsers, with proper error handling and etc.
|
||||
//Currently tag is ignored, and work of the library relies heavily on the field order guarantees.
|
||||
//Thus, backward-compability and extensions are not supported.
|
||||
printer->Print(vars, "val tag = input.readTag($fieldNumber$, WireType.LENGTH_DELIMITED)\n");
|
||||
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->Indent();
|
||||
printer->Print(vars, "$fieldName$[i - 1].mergeFrom(input)\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 = FieldGenerator(descriptor, enclosingClass);
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
// TODO: stub here, resolve name properly!
|
||||
vars["fieldType"] = underlyingType + ".Builder" + underlyingType;
|
||||
vars["initValue"] = getUnderlyingTypeInitValue();
|
||||
printer->Print(vars, "val tmp: $fieldType$ = $fieldType$()\n");
|
||||
singleFieldGen.simpleName = "tmp";
|
||||
singleFieldGen.modifier = FieldDescriptor::LABEL_OPTIONAL;
|
||||
singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ true);
|
||||
|
||||
printer->Print(vars, "$fieldName$.add(tmp.build())\n");
|
||||
|
||||
printer->Outdent();
|
||||
printer->Print("}\n");
|
||||
}
|
||||
else {
|
||||
printer->Print(vars, "if ($fieldName$.size > 0) {\n");
|
||||
printer->Indent();
|
||||
|
||||
// tag
|
||||
printer->Print(vars, "output.writeTag($fieldNumber$, WireType.LENGTH_DELIMITED)\n");
|
||||
|
||||
// length
|
||||
printer->Print(vars, "output.writeInt32NoTag($fieldName$.size)\n");
|
||||
printer->Print(vars, "output.writeInt32NoTag($fieldName$.size)\n");
|
||||
|
||||
// all elements
|
||||
printer->Print(vars, "for (item in $fieldName$) {\n");
|
||||
printer->Indent();
|
||||
|
||||
/* hack: copy current FieldGenerator and change label to OPTIONAL. 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 safely5
|
||||
removed as soon as target code will support inheritance and interfaces.
|
||||
(then writing CodedOutputStream.writeMessage will be possible).
|
||||
*/
|
||||
FieldGenerator singleFieldGen = FieldGenerator(descriptor);
|
||||
// hack: see above
|
||||
FieldGenerator singleFieldGen = FieldGenerator(descriptor, enclosingClass);
|
||||
singleFieldGen.simpleName = "item";
|
||||
singleFieldGen.modifier = FieldDescriptor::LABEL_OPTIONAL;
|
||||
singleFieldGen.generateSerializationCode(printer, isRead, /* noTag = */ true);
|
||||
|
||||
printer->Outdent(); // for-loop
|
||||
printer->Print("}\n");
|
||||
|
||||
printer->Outdent(); // if-clause
|
||||
printer->Print("}\n");
|
||||
}
|
||||
|
||||
printer->Outdent(); // if-clause
|
||||
printer->Print("}\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -244,10 +272,10 @@ void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead
|
||||
if (descriptor->type() == FieldDescriptor::TYPE_ENUM) {
|
||||
vars["converter"] = underlyingType + ".fromIntTo" + underlyingType;
|
||||
if (isRead) {
|
||||
printer->Print(vars, "$fieldName$ = $converter$(input.read$type$($fieldNumber$))\n");
|
||||
printer->Print(vars, "$fieldName$ = $converter$(input.read$type$($maybeFieldNumber$))\n");
|
||||
}
|
||||
else {
|
||||
printer->Print(vars, "output.write$type$ ($fieldNumber$, $fieldName$?.ord)\n");
|
||||
printer->Print(vars, "output.write$type$ ($maybeFieldNumber$$maybeComma$$fieldName$?.ord)\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -257,17 +285,13 @@ void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead
|
||||
that message
|
||||
*/
|
||||
if (descriptor->type() == FieldDescriptor::TYPE_MESSAGE) {
|
||||
vars["fieldName"] = noTag ? "item" : simpleName;
|
||||
vars["maybeNoTag"] = noTag ? "NoTag" : "";
|
||||
vars["fieldNumber"] = std::to_string(descriptor->number());
|
||||
vars["maybeNoTag"] = "NoTag";
|
||||
if (isRead) {
|
||||
printer->Print(vars,
|
||||
"val tag = input.readTag($fieldNumber$, WireType.LENGTH_DELIMITED)\n"
|
||||
"$fieldName$.readFrom$maybeNoTag$(input)\n");
|
||||
}
|
||||
else {
|
||||
printer->Print(vars,
|
||||
"output.writeTag($fieldNumber$, WireType.LENGTH_DELIMITED)\n"
|
||||
"$fieldName$.writeTo$maybeNoTag$(output)\n");
|
||||
}
|
||||
return;
|
||||
@@ -275,13 +299,14 @@ void FieldGenerator::generateSerializationCode(io::Printer *printer, bool isRead
|
||||
|
||||
/* Finally, serialize trivial cases */
|
||||
if (isRead) {
|
||||
printer->Print(vars, "$fieldName$ = input.read$type$($fieldNumber$)\n");
|
||||
printer->Print(vars, "$fieldName$ = input.read$type$($maybeFieldNumber$)\n");
|
||||
}
|
||||
else {
|
||||
printer->Print(vars, "output.write$type$ ($fieldNumber$, $fieldName$)\n");
|
||||
printer->Print(vars, "output.write$type$ ($maybeFieldNumber$$maybeComma$$fieldName$)\n");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: refactor in separate static class
|
||||
string FieldGenerator::protobufTypeToKotlinFunctionSuffix(FieldDescriptor::Type type) const {
|
||||
switch (type) {
|
||||
case FieldDescriptor::TYPE_DOUBLE:
|
||||
@@ -321,11 +346,11 @@ string FieldGenerator::protobufTypeToKotlinFunctionSuffix(FieldDescriptor::Type
|
||||
}
|
||||
}
|
||||
|
||||
void FieldGenerator::generateSetter(io::Printer *printer, string builderName) const {
|
||||
void FieldGenerator::generateSetter(io::Printer *printer) const {
|
||||
map <string, string> vars;
|
||||
vars["camelCaseName"] = name_resolving::makeFirstLetterUpper(simpleName);
|
||||
vars["fieldName"] = simpleName;
|
||||
vars["builderName"] = builderName;
|
||||
vars["builderName"] = enclosingClass->builderName;
|
||||
vars["type"] = fullType;
|
||||
printer->Print(vars,
|
||||
"fun set$camelCaseName$(value: $type$): $builderName$ {\n");
|
||||
@@ -337,12 +362,12 @@ void FieldGenerator::generateSetter(io::Printer *printer, string builderName) co
|
||||
printer->Print("}\n");
|
||||
}
|
||||
|
||||
void FieldGenerator::generateRepeatedMethods(io::Printer * printer, bool isBuilder, string builderName) const {
|
||||
void FieldGenerator::generateRepeatedMethods(io::Printer * printer, bool isBuilder) const {
|
||||
map <string, string> vars;
|
||||
vars["elementType"] = underlyingType;
|
||||
vars["arg"] = "value";
|
||||
vars["fieldName"] = simpleName;
|
||||
vars["builderName"] = builderName;
|
||||
vars["builderName"] = enclosingClass->builderName;
|
||||
|
||||
// generate indexed setter for builders
|
||||
if (isBuilder) {
|
||||
@@ -382,6 +407,13 @@ void FieldGenerator::generateRepeatedMethods(io::Printer * printer, bool isBuild
|
||||
#endif
|
||||
}
|
||||
|
||||
string FieldGenerator::getKotlinFunctionSuffix() const {
|
||||
return protobufTypeToKotlinFunctionSuffix(descriptor->type());
|
||||
}
|
||||
|
||||
string FieldGenerator::getUnderlyingTypeInitValue() const {
|
||||
return protobufTypeToInitValue(descriptor);
|
||||
}
|
||||
|
||||
} // namespace kotlin
|
||||
} // namespace compiler
|
||||
|
||||
+16
-6
@@ -8,19 +8,22 @@
|
||||
#include <vector>
|
||||
#include <google/protobuf/io/printer.h>
|
||||
#include <google/protobuf/descriptor.h>
|
||||
#include "kotlin_class_generator.h"
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
namespace compiler {
|
||||
namespace kotlin {
|
||||
|
||||
class ClassGenerator; // declared in kotlin_class_generator.h
|
||||
|
||||
class FieldGenerator {
|
||||
private:
|
||||
FieldDescriptor const * descriptor;
|
||||
string protobufToKotlinField() const;
|
||||
string protobufToKotlinType () const;
|
||||
string getInitValue() const;
|
||||
|
||||
// TODO: refactor from field generator to some static utility namespace
|
||||
/**
|
||||
* Converts one of protobuf wire types to corresponding Kotlin type with proper
|
||||
* naming, so it could be used as suffix after read/write, resulting in function
|
||||
@@ -30,10 +33,13 @@ private:
|
||||
* CodedInputStream.readSFixed32(fieldNumber: Int)
|
||||
*/
|
||||
string protobufTypeToKotlinFunctionSuffix(FieldDescriptor::Type type) const;
|
||||
void generateSetter(io::Printer * printer, string builderName) const;
|
||||
void generateRepeatedMethods(io::Printer * printer, bool isBuilder, string builderName) const;
|
||||
string protobufTypeToInitValue(FieldDescriptor const * descriptor) const;
|
||||
|
||||
void generateSetter(io::Printer * printer) const;
|
||||
void generateRepeatedMethods(io::Printer * printer, bool isBuilder) const;
|
||||
public:
|
||||
FieldDescriptor::Label modifier;
|
||||
ClassGenerator const * enclosingClass; // class, in which that field is defined
|
||||
string simpleName;
|
||||
string underlyingType; // unwrapped type.
|
||||
|
||||
@@ -44,11 +50,15 @@ public:
|
||||
* fullType = underlyingType for all other cases
|
||||
*/
|
||||
string fullType;
|
||||
string getInitValue() const;
|
||||
string getUnderlyingTypeInitValue() const;
|
||||
FieldDescriptor::Type protoType;
|
||||
int fieldNumber;
|
||||
|
||||
string initValue;
|
||||
void generateCode(io::Printer * printer, bool isBuilder, string className) const;
|
||||
void generateCode(io::Printer * printer, bool isBuilder) const;
|
||||
void generateSerializationCode(io::Printer * printer, bool isRead = false, bool noTag = false) const;
|
||||
FieldGenerator(FieldDescriptor const * descriptor);
|
||||
FieldGenerator(FieldDescriptor const * descriptor, ClassGenerator const * enclosingClass);
|
||||
string getKotlinFunctionSuffix() const;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -94,12 +94,33 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
|
||||
fun build(): PhoneNumber {
|
||||
return PhoneNumber(number, type)
|
||||
}
|
||||
|
||||
fun parseFieldFrom(input: CodedInputStream): Boolean {
|
||||
if (input.isAtEnd()) { return false }
|
||||
val tag = input.readInt32NoTag()
|
||||
if (tag == 0) { return false }
|
||||
val fieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val wireType = WireFormat.getTagWireType(tag)
|
||||
when(fieldNumber) {
|
||||
1 -> number = input.readStringNoTag()
|
||||
2 -> type = PhoneType.fromIntToPhoneType(input.readEnumNoTag())
|
||||
}
|
||||
return true}
|
||||
fun parseFrom(input: CodedInputStream): BuilderPhoneNumber {
|
||||
while(parseFieldFrom(input)) {}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun mergeWith (other: PhoneNumber) {
|
||||
number = other.number
|
||||
type = other.type
|
||||
}
|
||||
|
||||
fun mergeFrom (input: CodedInputStream) {
|
||||
number = input.readString(1)
|
||||
type = PhoneType.fromIntToPhoneType(input.readEnum(2))
|
||||
}
|
||||
val builder = BuilderPhoneNumber()
|
||||
mergeWith(builder.parseFrom(input).build())}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,9 +135,7 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
|
||||
if (phones.size > 0) {
|
||||
output.writeTag(4, WireType.LENGTH_DELIMITED)
|
||||
output.writeInt32NoTag(phones.size)
|
||||
output.writeInt32NoTag(phones.size)
|
||||
for (item in phones) {
|
||||
output.writeTag(4, WireType.LENGTH_DELIMITED)
|
||||
item.writeToNoTag(output)
|
||||
}
|
||||
}
|
||||
@@ -190,12 +209,12 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
|
||||
name = input.readString(1)
|
||||
id = input.readInt32(2)
|
||||
email = input.readString(3)
|
||||
if (phones.size > 0) {
|
||||
val tag = input.readTag(4, WireType.LENGTH_DELIMITED)
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
phones[i - 1].mergeFrom(input)
|
||||
}
|
||||
val tag = input.readTag(4, WireType.LENGTH_DELIMITED)
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
val tmp: PhoneNumber.BuilderPhoneNumber = PhoneNumber.BuilderPhoneNumber()
|
||||
tmp.readFromNoTag(input)
|
||||
phones.add(tmp.build())
|
||||
}
|
||||
someBytes = input.readBytes(5)
|
||||
return this
|
||||
@@ -204,21 +223,46 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
|
||||
fun build(): Person {
|
||||
return Person(name, id, email, phones, someBytes)
|
||||
}
|
||||
|
||||
fun parseFieldFrom(input: CodedInputStream): Boolean {
|
||||
if (input.isAtEnd()) { return false }
|
||||
val tag = input.readInt32NoTag()
|
||||
if (tag == 0) { return false }
|
||||
val fieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val wireType = WireFormat.getTagWireType(tag)
|
||||
when(fieldNumber) {
|
||||
1 -> name = input.readStringNoTag()
|
||||
2 -> id = input.readInt32NoTag()
|
||||
3 -> email = input.readStringNoTag()
|
||||
4 -> {
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
val tmp: PhoneNumber.BuilderPhoneNumber = PhoneNumber.BuilderPhoneNumber()
|
||||
tmp.readFromNoTag(input)
|
||||
phones.add(tmp.build())
|
||||
}
|
||||
}
|
||||
5 -> someBytes = input.readBytesNoTag()
|
||||
}
|
||||
return true}
|
||||
fun parseFrom(input: CodedInputStream): BuilderPerson {
|
||||
while(parseFieldFrom(input)) {}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun mergeWith (other: Person) {
|
||||
name = other.name
|
||||
id = other.id
|
||||
email = other.email
|
||||
phones.addAll(other.phones)
|
||||
someBytes?.plus(other.someBytes ?: ByteArray(0))
|
||||
}
|
||||
|
||||
fun mergeFrom (input: CodedInputStream) {
|
||||
name = input.readString(1)
|
||||
id = input.readInt32(2)
|
||||
email = input.readString(3)
|
||||
if (phones.size > 0) {
|
||||
val tag = input.readTag(4, WireType.LENGTH_DELIMITED)
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
phones[i - 1].mergeFrom(input)
|
||||
}
|
||||
}
|
||||
someBytes = input.readBytes(5)
|
||||
}
|
||||
val builder = BuilderPerson()
|
||||
mergeWith(builder.parseFrom(input).build())}
|
||||
}
|
||||
|
||||
|
||||
@@ -239,9 +283,7 @@ class AddressBook private constructor (people: MutableList <Person> = mutableLi
|
||||
if (people.size > 0) {
|
||||
output.writeTag(1, WireType.LENGTH_DELIMITED)
|
||||
output.writeInt32NoTag(people.size)
|
||||
output.writeInt32NoTag(people.size)
|
||||
for (item in people) {
|
||||
output.writeTag(1, WireType.LENGTH_DELIMITED)
|
||||
item.writeToNoTag(output)
|
||||
}
|
||||
}
|
||||
@@ -279,12 +321,12 @@ class AddressBook private constructor (people: MutableList <Person> = mutableLi
|
||||
}
|
||||
|
||||
fun readFromNoTag (input: CodedInputStream): BuilderAddressBook {
|
||||
if (people.size > 0) {
|
||||
val tag = input.readTag(1, WireType.LENGTH_DELIMITED)
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
people[i - 1].mergeFrom(input)
|
||||
}
|
||||
val tag = input.readTag(1, WireType.LENGTH_DELIMITED)
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
val tmp: Person.BuilderPerson = Person.BuilderPerson()
|
||||
tmp.readFromNoTag(input)
|
||||
people.add(tmp.build())
|
||||
}
|
||||
return this
|
||||
}
|
||||
@@ -292,17 +334,38 @@ class AddressBook private constructor (people: MutableList <Person> = mutableLi
|
||||
fun build(): AddressBook {
|
||||
return AddressBook(people)
|
||||
}
|
||||
|
||||
fun parseFieldFrom(input: CodedInputStream): Boolean {
|
||||
if (input.isAtEnd()) { return false }
|
||||
val tag = input.readInt32NoTag()
|
||||
if (tag == 0) { return false }
|
||||
val fieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val wireType = WireFormat.getTagWireType(tag)
|
||||
when(fieldNumber) {
|
||||
1 -> {
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
val tmp: Person.BuilderPerson = Person.BuilderPerson()
|
||||
tmp.readFromNoTag(input)
|
||||
people.add(tmp.build())
|
||||
}
|
||||
}
|
||||
}
|
||||
return true}
|
||||
fun parseFrom(input: CodedInputStream): BuilderAddressBook {
|
||||
while(parseFieldFrom(input)) {}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun mergeWith (other: AddressBook) {
|
||||
people.addAll(other.people)
|
||||
}
|
||||
|
||||
fun mergeFrom (input: CodedInputStream) {
|
||||
if (people.size > 0) {
|
||||
val tag = input.readTag(1, WireType.LENGTH_DELIMITED)
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
people[i - 1].mergeFrom(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
val builder = BuilderAddressBook()
|
||||
mergeWith(builder.parseFrom(input).build())}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -234,7 +234,7 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
|
||||
val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val actualWireType = WireFormat.getTagWireType(tag)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.LENGTH_DELIMITED, actualWireType)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, expectedWireType, actualWireType)
|
||||
return tag
|
||||
}
|
||||
|
||||
|
||||
@@ -94,12 +94,33 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
|
||||
fun build(): PhoneNumber {
|
||||
return PhoneNumber(number, type)
|
||||
}
|
||||
|
||||
fun parseFieldFrom(input: CodedInputStream): Boolean {
|
||||
if (input.isAtEnd()) { return false }
|
||||
val tag = input.readInt32NoTag()
|
||||
if (tag == 0) { return false }
|
||||
val fieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val wireType = WireFormat.getTagWireType(tag)
|
||||
when(fieldNumber) {
|
||||
1 -> number = input.readStringNoTag()
|
||||
2 -> type = PhoneType.fromIntToPhoneType(input.readEnumNoTag())
|
||||
}
|
||||
return true}
|
||||
fun parseFrom(input: CodedInputStream): BuilderPhoneNumber {
|
||||
while(parseFieldFrom(input)) {}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun mergeWith (other: PhoneNumber) {
|
||||
number = other.number
|
||||
type = other.type
|
||||
}
|
||||
|
||||
fun mergeFrom (input: CodedInputStream) {
|
||||
number = input.readString(1)
|
||||
type = PhoneType.fromIntToPhoneType(input.readEnum(2))
|
||||
}
|
||||
val builder = BuilderPhoneNumber()
|
||||
mergeWith(builder.parseFrom(input).build())}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,9 +135,7 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
|
||||
if (phones.size > 0) {
|
||||
output.writeTag(4, WireType.LENGTH_DELIMITED)
|
||||
output.writeInt32NoTag(phones.size)
|
||||
output.writeInt32NoTag(phones.size)
|
||||
for (item in phones) {
|
||||
output.writeTag(4, WireType.LENGTH_DELIMITED)
|
||||
item.writeToNoTag(output)
|
||||
}
|
||||
}
|
||||
@@ -190,12 +209,12 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
|
||||
name = input.readString(1)
|
||||
id = input.readInt32(2)
|
||||
email = input.readString(3)
|
||||
if (phones.size > 0) {
|
||||
val tag = input.readTag(4, WireType.LENGTH_DELIMITED)
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
phones[i - 1].mergeFrom(input)
|
||||
}
|
||||
val tag = input.readTag(4, WireType.LENGTH_DELIMITED)
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
val tmp: PhoneNumber.BuilderPhoneNumber = PhoneNumber.BuilderPhoneNumber()
|
||||
tmp.readFromNoTag(input)
|
||||
phones.add(tmp.build())
|
||||
}
|
||||
someBytes = input.readBytes(5)
|
||||
return this
|
||||
@@ -204,21 +223,46 @@ class Person private constructor (name: kotlin.String? = "", id: Int? = 0, email
|
||||
fun build(): Person {
|
||||
return Person(name, id, email, phones, someBytes)
|
||||
}
|
||||
|
||||
fun parseFieldFrom(input: CodedInputStream): Boolean {
|
||||
if (input.isAtEnd()) { return false }
|
||||
val tag = input.readInt32NoTag()
|
||||
if (tag == 0) { return false }
|
||||
val fieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val wireType = WireFormat.getTagWireType(tag)
|
||||
when(fieldNumber) {
|
||||
1 -> name = input.readStringNoTag()
|
||||
2 -> id = input.readInt32NoTag()
|
||||
3 -> email = input.readStringNoTag()
|
||||
4 -> {
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
val tmp: PhoneNumber.BuilderPhoneNumber = PhoneNumber.BuilderPhoneNumber()
|
||||
tmp.readFromNoTag(input)
|
||||
phones.add(tmp.build())
|
||||
}
|
||||
}
|
||||
5 -> someBytes = input.readBytesNoTag()
|
||||
}
|
||||
return true}
|
||||
fun parseFrom(input: CodedInputStream): BuilderPerson {
|
||||
while(parseFieldFrom(input)) {}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun mergeWith (other: Person) {
|
||||
name = other.name
|
||||
id = other.id
|
||||
email = other.email
|
||||
phones.addAll(other.phones)
|
||||
someBytes?.plus(other.someBytes ?: ByteArray(0))
|
||||
}
|
||||
|
||||
fun mergeFrom (input: CodedInputStream) {
|
||||
name = input.readString(1)
|
||||
id = input.readInt32(2)
|
||||
email = input.readString(3)
|
||||
if (phones.size > 0) {
|
||||
val tag = input.readTag(4, WireType.LENGTH_DELIMITED)
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
phones[i - 1].mergeFrom(input)
|
||||
}
|
||||
}
|
||||
someBytes = input.readBytes(5)
|
||||
}
|
||||
val builder = BuilderPerson()
|
||||
mergeWith(builder.parseFrom(input).build())}
|
||||
}
|
||||
|
||||
|
||||
@@ -239,9 +283,7 @@ class AddressBook private constructor (people: MutableList <Person> = mutableLi
|
||||
if (people.size > 0) {
|
||||
output.writeTag(1, WireType.LENGTH_DELIMITED)
|
||||
output.writeInt32NoTag(people.size)
|
||||
output.writeInt32NoTag(people.size)
|
||||
for (item in people) {
|
||||
output.writeTag(1, WireType.LENGTH_DELIMITED)
|
||||
item.writeToNoTag(output)
|
||||
}
|
||||
}
|
||||
@@ -279,12 +321,12 @@ class AddressBook private constructor (people: MutableList <Person> = mutableLi
|
||||
}
|
||||
|
||||
fun readFromNoTag (input: CodedInputStream): BuilderAddressBook {
|
||||
if (people.size > 0) {
|
||||
val tag = input.readTag(1, WireType.LENGTH_DELIMITED)
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
people[i - 1].mergeFrom(input)
|
||||
}
|
||||
val tag = input.readTag(1, WireType.LENGTH_DELIMITED)
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
val tmp: Person.BuilderPerson = Person.BuilderPerson()
|
||||
tmp.readFromNoTag(input)
|
||||
people.add(tmp.build())
|
||||
}
|
||||
return this
|
||||
}
|
||||
@@ -292,17 +334,38 @@ class AddressBook private constructor (people: MutableList <Person> = mutableLi
|
||||
fun build(): AddressBook {
|
||||
return AddressBook(people)
|
||||
}
|
||||
|
||||
fun parseFieldFrom(input: CodedInputStream): Boolean {
|
||||
if (input.isAtEnd()) { return false }
|
||||
val tag = input.readInt32NoTag()
|
||||
if (tag == 0) { return false }
|
||||
val fieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val wireType = WireFormat.getTagWireType(tag)
|
||||
when(fieldNumber) {
|
||||
1 -> {
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
val tmp: Person.BuilderPerson = Person.BuilderPerson()
|
||||
tmp.readFromNoTag(input)
|
||||
people.add(tmp.build())
|
||||
}
|
||||
}
|
||||
}
|
||||
return true}
|
||||
fun parseFrom(input: CodedInputStream): BuilderAddressBook {
|
||||
while(parseFieldFrom(input)) {}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun mergeWith (other: AddressBook) {
|
||||
people.addAll(other.people)
|
||||
}
|
||||
|
||||
fun mergeFrom (input: CodedInputStream) {
|
||||
if (people.size > 0) {
|
||||
val tag = input.readTag(1, WireType.LENGTH_DELIMITED)
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
people[i - 1].mergeFrom(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
val builder = BuilderAddressBook()
|
||||
mergeWith(builder.parseFrom(input).build())}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -16,14 +16,20 @@ fun testMessageSerialization() {
|
||||
.setEmail("wtf@dasda.com") // all setters return this builder, so you could chain modifiers in LINQ-style
|
||||
.setId(42)
|
||||
.setName("John Doe")
|
||||
.setPhones(arrayOf( // repeated fields stored as Array<>, so use arrayOf() for creating repeated fields
|
||||
.addPhoneNumber( // repeated fields stored as Array<>, so use arrayOf() for creating repeated fields
|
||||
Person.PhoneNumber.BuilderPhoneNumber()
|
||||
.setNumber("342143-23423-42")
|
||||
.setType(Person.PhoneType.HOME)
|
||||
.build()
|
||||
))
|
||||
)
|
||||
.addPhoneNumber(
|
||||
Person.PhoneNumber.BuilderPhoneNumber()
|
||||
.setNumber("8-800-555-35-35")
|
||||
.setType(Person.PhoneType.WORK)
|
||||
.build()
|
||||
)
|
||||
.build() // don't forget to call build() to produce message
|
||||
msg.writeTo(outs)
|
||||
msg.writeToNoTag(outs)
|
||||
|
||||
// Now let's use output stream as input to read our message from it!
|
||||
var ins = CodedInputStream(ByteArrayInputStream(s.toByteArray()))
|
||||
|
||||
Reference in New Issue
Block a user