Supported nested messages, repeated fields in Proto compiler. Fixed Kotin runtime appropriately.
This commit is contained in:
@@ -13,53 +13,62 @@ namespace compiler {
|
||||
namespace kotlin {
|
||||
|
||||
void ClassGenerator::generateCode(io::Printer *printer, bool isBuilder) const {
|
||||
// print class header
|
||||
map<string, string> vars;
|
||||
vars["modifier"] = modifier.getName();
|
||||
vars["name"] = (isBuilder? "Builder" : "") + simpleName;
|
||||
printer->Print(vars,
|
||||
"$modifier$ $name$ private constructor () {"
|
||||
"\n"
|
||||
);
|
||||
generateHeader(printer, isBuilder);
|
||||
printer->Indent();
|
||||
|
||||
// generate code for nested classes declarations
|
||||
for (ClassGenerator *gen: classesDeclarations) {
|
||||
gen->generateCode(printer, isBuilder);
|
||||
printer->Print("\n\n"); // separate each definition from next code block with empty line
|
||||
}
|
||||
|
||||
// generate code for nested enums declarations
|
||||
for (EnumGenerator *gen: enumsDeclaraions) {
|
||||
gen->generateCode(printer);
|
||||
printer->Print("\n\n"); // separate each definitions from next code block with empty line
|
||||
}
|
||||
|
||||
// generate code for fields
|
||||
/**
|
||||
* Field generator should know if it is generating code for builder.
|
||||
* or for fair class to choose between 'val' and 'var'.
|
||||
*/
|
||||
for (FieldGenerator *gen: properties) {
|
||||
gen->generateCode(printer);
|
||||
gen->generateCode(printer, isBuilder);
|
||||
printer->Print("\n");
|
||||
}
|
||||
|
||||
// generate constructor for builders
|
||||
if (isBuilder) {
|
||||
printer->Print("\n");
|
||||
generateConstructor(printer);
|
||||
printer->Print("\n");
|
||||
generateInitSection(printer);
|
||||
|
||||
// enum declarations and nested classes declarations only for fair classes
|
||||
if (!isBuilder) {
|
||||
for (EnumGenerator *gen: enumsDeclaraions) {
|
||||
gen->generateCode(printer);
|
||||
printer->Print("\n");
|
||||
}
|
||||
|
||||
for (ClassGenerator *gen: classesDeclarations) {
|
||||
gen->generateCode(printer);
|
||||
printer->Print("\n");
|
||||
}
|
||||
}
|
||||
|
||||
// generate builder for fair classes
|
||||
// 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 and mergeFrom only for fair classes
|
||||
if (!isBuilder) {
|
||||
printer->Print("\n");
|
||||
generateBuilder(printer);
|
||||
|
||||
printer->Print("\n");
|
||||
generateMergeFrom(printer);
|
||||
}
|
||||
|
||||
// build() is only for builders
|
||||
if (isBuilder) {
|
||||
printer->Print("\n");
|
||||
generateBuildMethod(printer);
|
||||
}
|
||||
|
||||
printer->Outdent();
|
||||
printer->Print("}");
|
||||
printer->Print("}\n");
|
||||
}
|
||||
|
||||
ClassGenerator::ClassGenerator(Descriptor const *descriptor) {
|
||||
ClassGenerator::ClassGenerator(Descriptor const *descriptor)
|
||||
: descriptor(descriptor) {
|
||||
simpleName = descriptor->name(); // TODO: think about more careful class naming
|
||||
modifier = ClassModifier(ClassModifier::CLASS);
|
||||
|
||||
int field_count = descriptor->field_count();
|
||||
for (int i = 0; i < field_count; ++i) {
|
||||
@@ -96,52 +105,124 @@ ClassGenerator::~ClassGenerator() {
|
||||
}
|
||||
}
|
||||
|
||||
void ClassGenerator::generateBuilder(io::Printer *) const {
|
||||
|
||||
void ClassGenerator::generateBuilder(io::Printer * printer) const {
|
||||
//XXX: just reuse generateCode with flag isBuilder set
|
||||
generateCode(printer, /* isBuilder = */ true);
|
||||
}
|
||||
|
||||
void ClassGenerator::generateConstructor(io::Printer *printer, bool isBuilder) const {
|
||||
// generate header
|
||||
printer->Print("private constructor(\n");
|
||||
void ClassGenerator::generateMergeFrom(io::Printer * printer) const {
|
||||
//TODO: Looks pretty dirty. Should reconsider process of generating readFrom, mergeFrom and writeTo.
|
||||
map <string, string> vars;
|
||||
printer->Print(vars, "fun mergeFrom (input: CodedInputStream) {\n");
|
||||
printer->Indent();
|
||||
|
||||
// place each argument of constructor in separate line for a prettier code
|
||||
// we indent twice to make arguments indentation larger than indentation of inner block
|
||||
printer->Indent();
|
||||
printer->Indent();
|
||||
for (int i = 0; i < properties.size(); ++i) {
|
||||
// generate argument definition
|
||||
map<string, string> vars;
|
||||
vars["name"] = properties[i] ->simpleName;
|
||||
vars["field"] = properties[i]->fieldName;
|
||||
printer->Print(vars,
|
||||
"$name$: $field$");
|
||||
|
||||
// if it's last property, then print closing bracket for argument list, otherwise put comma
|
||||
if (i + 1 == properties.size()) {
|
||||
printer->Print(") : this()\n");
|
||||
printer->Outdent();
|
||||
printer->Outdent();
|
||||
printer->Print("{");
|
||||
printer->Indent();
|
||||
}
|
||||
else {
|
||||
printer->Print(",");
|
||||
}
|
||||
|
||||
printer->Print("\n");
|
||||
properties[i]->generateSerializationCode(printer, /* isRead = */ true);
|
||||
}
|
||||
|
||||
// print body of constructor - just assign arguments to corresponding fields
|
||||
printer->Outdent();
|
||||
printer->Print("}\n");
|
||||
}
|
||||
|
||||
void ClassGenerator::generateSerializers(io::Printer * printer, bool isRead) const {
|
||||
map <string, string> vars;
|
||||
vars["funName"]= isRead ? "readFrom" : "writeTo";
|
||||
vars["stream"] = isRead ? "CodedInputStream" : "CodedOutputStream";
|
||||
vars["arg"] = isRead ? "input" : "output";
|
||||
vars["maybeSeparator"] = isRead ? "" : ", ";
|
||||
|
||||
// generate function header
|
||||
printer->Print(vars,
|
||||
"fun $funName$ ($arg$: $stream$) {"
|
||||
"\n");
|
||||
printer->Indent();
|
||||
|
||||
//TODO: write message tag and size
|
||||
printer->Print(vars, "$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["stream"] = isRead ? "CodedInputStream" : "CodedOutputStream";
|
||||
vars["arg"] = isRead ? "input" : "output";
|
||||
|
||||
// generate function header
|
||||
printer->Print(vars,
|
||||
"fun $funName$ ($arg$: $stream$) {"
|
||||
"\n");
|
||||
printer->Indent();
|
||||
|
||||
// generate code for serialization/deserialization of fields
|
||||
for (int i = 0; i < properties.size(); ++i) {
|
||||
properties[i]->generateSerializationCode(printer, isRead);
|
||||
}
|
||||
|
||||
printer->Outdent();
|
||||
printer->Print("}\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
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]->fieldName;
|
||||
if (i + 1 != properties.size()) {
|
||||
argumentList += ", ";
|
||||
}
|
||||
}
|
||||
|
||||
map<string, string> vars;
|
||||
vars["name"] = (isBuilder? "Builder" : "") + simpleName;
|
||||
vars["argumentList"] = argumentList;
|
||||
vars["maybePrivate"] = isBuilder? "" : " private";
|
||||
printer->Print(vars,
|
||||
"class $name$$maybePrivate$ constructor ($argumentList$) {"
|
||||
"\n"
|
||||
);
|
||||
}
|
||||
|
||||
void ClassGenerator::generateBuildMethod(io::Printer * printer) const {
|
||||
map <string, string> vars;
|
||||
vars["returnType"] = simpleName;
|
||||
printer->Print(vars,
|
||||
"fun build(): $returnType$ {\n");
|
||||
printer->Indent();
|
||||
|
||||
// pass all fields to constructor of enclosing class
|
||||
printer->Print(vars,
|
||||
"return $returnType$(");
|
||||
for (int i = 0; i < properties.size(); ++i) {
|
||||
printer->Print(properties[i]->simpleName.c_str());
|
||||
if (i + 1 != properties.size()) {
|
||||
printer->Print(", ");
|
||||
}
|
||||
}
|
||||
printer->Print(")\n");
|
||||
printer->Outdent();
|
||||
printer->Print("}\n");
|
||||
}
|
||||
|
||||
void ClassGenerator::generateInitSection(io::Printer * printer) const {
|
||||
printer->Print("init {\n");
|
||||
printer->Indent();
|
||||
|
||||
for (int i = 0; i < properties.size(); ++i) {
|
||||
map <string, string> vars;
|
||||
vars["name"] = properties[i]->simpleName;
|
||||
printer->Print(vars,
|
||||
"this.$name$ = $name$"
|
||||
"\n"
|
||||
"this.$name$ = $name$"
|
||||
"\n"
|
||||
);
|
||||
}
|
||||
|
||||
printer->Outdent();
|
||||
printer->Print("}");
|
||||
printer->Print("}\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ public:
|
||||
|
||||
class ClassGenerator {
|
||||
public:
|
||||
ClassModifier modifier;
|
||||
string simpleName;
|
||||
vector <FieldGenerator *> properties;
|
||||
vector <ClassGenerator *> classesDeclarations;
|
||||
@@ -41,15 +40,30 @@ public:
|
||||
ClassGenerator (Descriptor const * descriptor);
|
||||
~ClassGenerator ();
|
||||
|
||||
|
||||
void generateCode (io::Printer * printer, bool isBuilder = false) const;
|
||||
private:
|
||||
Descriptor const * descriptor;
|
||||
void generateBuilder (io::Printer * printer) const;
|
||||
void generateBuildMethod (io::Printer * printer) const;
|
||||
void generateInitSection (io::Printer * printer) const;
|
||||
|
||||
/**
|
||||
* Flag isBuilder used for reducing code repeating, as code for class itself
|
||||
* and for its inner builder are structurally very alike and can be generated
|
||||
* with very little differences (like changing 'val's to 'var's and etc.)
|
||||
*/
|
||||
void generateCode (io::Printer * printer, bool isBuilder = false) const;
|
||||
private:
|
||||
void generateBuilder (io::Printer * printer) const;
|
||||
void generateConstructor(io::Printer * printer, bool isBuilder = false) const;
|
||||
void generateHeader(io::Printer * printer, bool isBuilder = false) const;
|
||||
|
||||
/**
|
||||
* IsRead flag indicates that readFrom method should be generated, otherwise
|
||||
* writeTo method is generated. Motivation is similar to the isBuilder flag:
|
||||
* 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 generateMergeFrom(io::Printer * printer) const;
|
||||
};
|
||||
|
||||
} // namespace kotlin
|
||||
|
||||
@@ -47,12 +47,58 @@ void EnumGenerator::generateCode(io::Printer * printer) const {
|
||||
if (i + 1 != enumValues.size()) {
|
||||
printer->Print(",");
|
||||
}
|
||||
else {
|
||||
printer->Print(";"); // semicolon is necessary as companion object will follow
|
||||
}
|
||||
printer->Print("\n");
|
||||
}
|
||||
|
||||
printer->Print("\n");
|
||||
gemerateEnumConverters(printer);
|
||||
|
||||
printer->Outdent();
|
||||
printer->Print("}");
|
||||
}
|
||||
|
||||
void EnumGenerator::gemerateEnumConverters(io::Printer *printer) const {
|
||||
map <string, string> vars;
|
||||
vars["dollar"] = "$";
|
||||
vars["type"] = simpleName;
|
||||
|
||||
printer->Print("companion object {\n");
|
||||
printer->Indent();
|
||||
|
||||
printer->Print(vars, "fun fromIntTo$type$ (ord: Int): $type$ {\n");
|
||||
printer->Indent();
|
||||
|
||||
printer->Print("return when (ord) {\n");
|
||||
printer->Indent();
|
||||
|
||||
// map ints to enum values
|
||||
for (int j = 0; j < enumValues.size(); ++j) {
|
||||
vars["ordinal"] = std::to_string(enumValues[j]->ordinal);
|
||||
vars["value"] = enumValues[j]->simpleName;
|
||||
|
||||
printer->Print(vars,
|
||||
"$ordinal$ -> $type$.$value$\n");
|
||||
}
|
||||
|
||||
// catch cast errors in else-clause
|
||||
printer->Print(vars,
|
||||
"else -> throw InvalidProtocolBufferException("
|
||||
"\"Error: got unexpected int $dollar${ord} while parsing $type$ \""
|
||||
");\n");
|
||||
|
||||
printer->Outdent(); // when-clause
|
||||
printer->Print("}\n");
|
||||
|
||||
printer->Outdent(); // function body
|
||||
printer->Print("}\n");
|
||||
|
||||
printer->Outdent(); // companion object body
|
||||
printer->Print("}\n");
|
||||
}
|
||||
|
||||
EnumGenerator::~EnumGenerator() {
|
||||
for (int i = 0; i < enumValues.size(); ++i) {
|
||||
delete enumValues[i];
|
||||
|
||||
@@ -30,6 +30,9 @@ public:
|
||||
vector <EnumValueGenerator *> enumValues;
|
||||
|
||||
void generateCode(io::Printer *) const;
|
||||
|
||||
private:
|
||||
void gemerateEnumConverters(io::Printer *printer) const;
|
||||
};
|
||||
|
||||
} // namespace kotlin
|
||||
|
||||
@@ -28,7 +28,7 @@ string FieldGenerator::protobufToKotlinField() const {
|
||||
postamble = "?";
|
||||
break;
|
||||
case FieldDescriptor::LABEL_REPEATED:
|
||||
preamble = "List <";
|
||||
preamble = "Array <";
|
||||
postamble = "> ";
|
||||
break;
|
||||
}
|
||||
@@ -90,16 +90,18 @@ string FieldGenerator::getInitValue() const {
|
||||
return fieldName + "()";
|
||||
}
|
||||
|
||||
void FieldGenerator::generateCode(io::Printer *printer) const {
|
||||
/** Generate Kotlin-code for field.
|
||||
* Note that we use 'val' everywhere, as we want messages to be immutable.
|
||||
* For constructing Messages corresponding Builders should be used.
|
||||
*/
|
||||
void FieldGenerator::generateCode(io::Printer *printer, bool isBuilder) const {
|
||||
map<string, string> vars;
|
||||
vars["name"] = simpleName;
|
||||
vars["field"] = protobufToKotlinField();
|
||||
vars["initValue"] = initValue;
|
||||
printer->Print(vars, "val $name$ : $field$ = $initValue$");
|
||||
printer->Print(vars, "var $name$ : $field$\n");
|
||||
|
||||
// make setter private for fair classes
|
||||
if (!isBuilder) {
|
||||
printer->Indent();
|
||||
printer->Print("private set\n");
|
||||
printer->Outdent();
|
||||
}
|
||||
}
|
||||
|
||||
FieldGenerator::FieldGenerator(FieldDescriptor const * descriptor)
|
||||
@@ -107,9 +109,159 @@ FieldGenerator::FieldGenerator(FieldDescriptor const * descriptor)
|
||||
, modifier(descriptor->label())
|
||||
, simpleName(descriptor->name())
|
||||
, fieldName(protobufToKotlinField())
|
||||
, fieldType(protobufToKotlinType())
|
||||
, initValue(getInitValue())
|
||||
{ }
|
||||
|
||||
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["fieldName"] = simpleName;
|
||||
vars["arg"] = isRead ? "input" : "output";
|
||||
|
||||
/**
|
||||
* First of all, try to generate syntax for repeated fields because it's separate case.
|
||||
* Do this according to 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
|
||||
*/
|
||||
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 fields order is critical for serialization/deserialization. Therefore,
|
||||
//backward-compability and extensions are not supported.
|
||||
printer->Print(vars, "val tag = input.readTag()\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");
|
||||
printer->Outdent();
|
||||
printer->Print("}\n");
|
||||
}
|
||||
else {
|
||||
// length
|
||||
printer->Print(vars, "$arg$.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.
|
||||
However, this hack isn't necessary 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);
|
||||
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");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Then check for conversions 'int -> enum-value' and \enum-value -> int' if current
|
||||
field is enum.
|
||||
This is necessary, because CodedStream stores enums as Ints in wire, delegating
|
||||
responsibility for casting those Ints to enum values and vice versa to the caller.
|
||||
Example: enumField = fromIntToMyEnumName(input.readEnum(42))
|
||||
Example: output.writeEnum(42, enumField.ord)
|
||||
*/
|
||||
if (descriptor->type() == FieldDescriptor::TYPE_ENUM) {
|
||||
vars["converter"] = fieldType + ".fromIntTo" + fieldType;
|
||||
if (isRead) {
|
||||
printer->Print(vars, "$fieldName$ = $converter$(input.read$type$($fieldNumber$))\n");
|
||||
}
|
||||
else {
|
||||
printer->Print(vars, "output.write$type$ ($fieldNumber$, $fieldName$?.ord)\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Then check for nested messages. Then we re-use writeTo method, that should be defined in
|
||||
that message
|
||||
*/
|
||||
if (descriptor->type() == FieldDescriptor::TYPE_MESSAGE) {
|
||||
vars["fieldName"] = noTag ? "item" : simpleName;
|
||||
vars["maybeNoTag"] = noTag ? "NoTag" : "";
|
||||
if (isRead) {
|
||||
printer->Print(vars, "$fieldName$.readFrom$maybeNoTag$(input)\n");
|
||||
}
|
||||
else {
|
||||
printer->Print(vars, "$fieldName$.writeTo$maybeNoTag$(output)\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Finally, serialize trivial cases */
|
||||
if (isRead) {
|
||||
printer->Print(vars, "$fieldName$ = input.read$type$($fieldNumber$)\n");
|
||||
}
|
||||
else {
|
||||
printer->Print(vars, "output.write$type$ ($fieldNumber$, $fieldName$)\n");
|
||||
}
|
||||
|
||||
|
||||
// TODO: support tricky types like enums/messages/repeated fields/etc
|
||||
}
|
||||
|
||||
// TODO: think about refactoring this method to FieldGenerator, as it is related to field, not to Class in general
|
||||
string FieldGenerator::protobufTypeToKotlinFunctionSuffix(FieldDescriptor::Type type) const {
|
||||
switch (type) {
|
||||
case FieldDescriptor::TYPE_DOUBLE:
|
||||
return "Double";
|
||||
case FieldDescriptor::TYPE_FLOAT:
|
||||
return "Float";
|
||||
case FieldDescriptor::TYPE_INT64:
|
||||
return "Int64";
|
||||
case FieldDescriptor::TYPE_UINT64:
|
||||
return "UInt64";
|
||||
case FieldDescriptor::TYPE_INT32:
|
||||
return "Int32";
|
||||
case FieldDescriptor::TYPE_FIXED64:
|
||||
return "Fixed64";
|
||||
case FieldDescriptor::TYPE_FIXED32:
|
||||
return "Fixed32";
|
||||
case FieldDescriptor::TYPE_BOOL:
|
||||
return "Bool";
|
||||
case FieldDescriptor::TYPE_STRING:
|
||||
return "String";
|
||||
case FieldDescriptor::TYPE_GROUP:
|
||||
return ""; // deprecated // TODO: think about proper error handling here
|
||||
case FieldDescriptor::TYPE_MESSAGE:
|
||||
return "Message"; // TODO: support messages
|
||||
case FieldDescriptor::TYPE_BYTES:
|
||||
return ""; // TODO: support bytes
|
||||
case FieldDescriptor::TYPE_UINT32:
|
||||
return "UInt32";
|
||||
case FieldDescriptor::TYPE_ENUM:
|
||||
return "Enum";
|
||||
case FieldDescriptor::TYPE_SFIXED32:
|
||||
return "SFixed32";
|
||||
case FieldDescriptor::TYPE_SFIXED64:
|
||||
return "SFixed64";
|
||||
case FieldDescriptor::TYPE_SINT32:
|
||||
return "SInt32";
|
||||
case FieldDescriptor::TYPE_SINT64:
|
||||
return "SInt64";
|
||||
}
|
||||
}
|
||||
} // namespace kotlin
|
||||
} // namspace compiler
|
||||
} // namespace protobuf
|
||||
|
||||
@@ -21,13 +21,24 @@ private:
|
||||
string protobufToKotlinType () const;
|
||||
string getInitValue() const;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* in CodedInputStream/CodedOutputStream.
|
||||
* Example: protobufToKotlinFunctionSuffix(TYPE_SFIXED32) returns "SFixed32", and
|
||||
* in Kotlin runtime exists method
|
||||
* CodedInputStream.readSFixed32(fieldNumber: Int)
|
||||
*/
|
||||
string protobufTypeToKotlinFunctionSuffix(FieldDescriptor::Type type) const;
|
||||
|
||||
public:
|
||||
FieldDescriptor::Label modifier;
|
||||
string simpleName;
|
||||
string fieldName;
|
||||
string fieldType;
|
||||
string initValue;
|
||||
|
||||
void generateCode(io::Printer *) const;
|
||||
void generateCode(io::Printer * printer, bool isBuilder = false) const;
|
||||
void generateSerializationCode(io::Printer * printer, bool isRead = false, bool noTag = false) const;
|
||||
FieldGenerator(FieldDescriptor const * descriptor);
|
||||
|
||||
};
|
||||
|
||||
@@ -1,25 +1,221 @@
|
||||
class Person private constructor () {
|
||||
class PhoneNumber private constructor () {
|
||||
val number : kotlin.String? = null
|
||||
val type : PhoneType? = null
|
||||
class Person private constructor (name: kotlin.String?, id: Int?, email: kotlin.String?, phones: Array <PhoneNumber> ) {
|
||||
var name : kotlin.String?
|
||||
private set
|
||||
|
||||
var id : Int?
|
||||
private set
|
||||
|
||||
var email : kotlin.String?
|
||||
private set
|
||||
|
||||
var phones : Array <PhoneNumber>
|
||||
private set
|
||||
|
||||
|
||||
init {
|
||||
this.name = name
|
||||
this.id = id
|
||||
this.email = email
|
||||
this.phones = phones
|
||||
}
|
||||
|
||||
enum class PhoneType(val ord: Int) {
|
||||
MOBILE (0),
|
||||
HOME (1),
|
||||
WORK (2)
|
||||
WORK (2);
|
||||
|
||||
companion object {
|
||||
fun fromIntToPhoneType (ord: Int): PhoneType {
|
||||
return when (ord) {
|
||||
0 -> PhoneType.MOBILE
|
||||
1 -> PhoneType.HOME
|
||||
2 -> PhoneType.WORK
|
||||
else -> throw InvalidProtocolBufferException("Error: got unexpected int ${ord} while parsing PhoneType ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
class PhoneNumber private constructor (number: kotlin.String?, type: PhoneType?) {
|
||||
var number : kotlin.String?
|
||||
private set
|
||||
|
||||
var type : PhoneType?
|
||||
private set
|
||||
|
||||
|
||||
init {
|
||||
this.number = number
|
||||
this.type = type
|
||||
}
|
||||
|
||||
fun writeTo (output: CodedOutputStream) {
|
||||
writeToNoTag(output)
|
||||
}
|
||||
|
||||
fun writeToNoTag (output: CodedOutputStream) {
|
||||
output.writeString (1, number)
|
||||
output.writeEnum (2, type?.ord)
|
||||
}
|
||||
|
||||
class BuilderPhoneNumber constructor (number: kotlin.String?, type: PhoneType?) {
|
||||
var number : kotlin.String?
|
||||
|
||||
var type : PhoneType?
|
||||
|
||||
|
||||
init {
|
||||
this.number = number
|
||||
this.type = type
|
||||
}
|
||||
|
||||
fun readFrom (input: CodedInputStream) {
|
||||
readFromNoTag(input)
|
||||
}
|
||||
|
||||
fun readFromNoTag (input: CodedInputStream) {
|
||||
number = input.readString(1)
|
||||
type = PhoneType.fromIntToPhoneType(input.readEnum(2))
|
||||
}
|
||||
|
||||
fun build(): PhoneNumber {
|
||||
return PhoneNumber(number, type)
|
||||
}
|
||||
}
|
||||
|
||||
fun mergeFrom (input: CodedInputStream) {
|
||||
number = input.readString(1)
|
||||
type = PhoneType.fromIntToPhoneType(input.readEnum(2))
|
||||
}
|
||||
}
|
||||
|
||||
val name : kotlin.String? = null
|
||||
val id : Int? = null
|
||||
val email : kotlin.String? = null
|
||||
val phones : List <PhoneNumber> = listOf()
|
||||
|
||||
fun writeTo (output: CodedOutputStream) {
|
||||
writeToNoTag(output)
|
||||
}
|
||||
|
||||
fun writeToNoTag (output: CodedOutputStream) {
|
||||
output.writeString (1, name)
|
||||
output.writeInt32 (2, id)
|
||||
output.writeString (3, email)
|
||||
if (phones.size > 0) {
|
||||
output.writeInt32NoTag(phones.size)
|
||||
for (item in phones) {
|
||||
item.writeToNoTag(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BuilderPerson constructor (name: kotlin.String?, id: Int?, email: kotlin.String?, phones: Array <PhoneNumber> ) {
|
||||
var name : kotlin.String?
|
||||
|
||||
var id : Int?
|
||||
|
||||
var email : kotlin.String?
|
||||
|
||||
var phones : Array <PhoneNumber>
|
||||
|
||||
|
||||
init {
|
||||
this.name = name
|
||||
this.id = id
|
||||
this.email = email
|
||||
this.phones = phones
|
||||
}
|
||||
|
||||
fun readFrom (input: CodedInputStream) {
|
||||
readFromNoTag(input)
|
||||
}
|
||||
|
||||
fun readFromNoTag (input: CodedInputStream) {
|
||||
name = input.readString(1)
|
||||
id = input.readInt32(2)
|
||||
email = input.readString(3)
|
||||
if (phones.size > 0) {
|
||||
val tag = input.readTag()
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
phones[i - 1].mergeFrom(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun build(): Person {
|
||||
return Person(name, id, email, phones)
|
||||
}
|
||||
}
|
||||
|
||||
fun mergeFrom (input: CodedInputStream) {
|
||||
name = input.readString(1)
|
||||
id = input.readInt32(2)
|
||||
email = input.readString(3)
|
||||
if (phones.size > 0) {
|
||||
val tag = input.readTag()
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
phones[i - 1].mergeFrom(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AddressBook private constructor () {
|
||||
val people : List <Person> = listOf()
|
||||
|
||||
class AddressBook private constructor (people: Array <Person> ) {
|
||||
var people : Array <Person>
|
||||
private set
|
||||
|
||||
|
||||
init {
|
||||
this.people = people
|
||||
}
|
||||
|
||||
fun writeTo (output: CodedOutputStream) {
|
||||
writeToNoTag(output)
|
||||
}
|
||||
|
||||
fun writeToNoTag (output: CodedOutputStream) {
|
||||
if (people.size > 0) {
|
||||
output.writeInt32NoTag(people.size)
|
||||
for (item in people) {
|
||||
item.writeToNoTag(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BuilderAddressBook constructor (people: Array <Person> ) {
|
||||
var people : Array <Person>
|
||||
|
||||
|
||||
init {
|
||||
this.people = people
|
||||
}
|
||||
|
||||
fun readFrom (input: CodedInputStream) {
|
||||
readFromNoTag(input)
|
||||
}
|
||||
|
||||
fun readFromNoTag (input: CodedInputStream) {
|
||||
if (people.size > 0) {
|
||||
val tag = input.readTag()
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
people[i - 1].mergeFrom(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun build(): AddressBook {
|
||||
return AddressBook(people)
|
||||
}
|
||||
}
|
||||
|
||||
fun mergeFrom (input: CodedInputStream) {
|
||||
if (people.size > 0) {
|
||||
val tag = input.readTag()
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
people[i - 1].mergeFrom(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+2546
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
class Person {
|
||||
class PhoneNumber {
|
||||
val number : kotlin.String?
|
||||
val type : PhoneType?
|
||||
}
|
||||
enum class PhoneType(val ord: Int) {
|
||||
MOBILE (0),
|
||||
HOME (1),
|
||||
WORK (2)
|
||||
}
|
||||
val name : kotlin.String?
|
||||
val id : Int?
|
||||
val email : kotlin.String?
|
||||
val phones : List <PhoneNumber>
|
||||
}
|
||||
class AddressBook {
|
||||
val people : List <Person>
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Created by user on 7/13/16.
|
||||
*/
|
||||
class AddressBook private constructor (people: Array <Person> ) {
|
||||
var people : Array <Person>
|
||||
private set
|
||||
|
||||
init {
|
||||
this.people = people
|
||||
}
|
||||
|
||||
fun writeTo (output: CodedOutputStream) {
|
||||
writeToNoTag(output)
|
||||
}
|
||||
|
||||
fun writeToNoTag (output: CodedOutputStream) {
|
||||
if (people.size > 0) {
|
||||
output.writeInt32NoTag(people.size)
|
||||
for (item in people) {
|
||||
item.writeToNoTag(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BuilderAddressBook constructor (people: Array <Person> ) {
|
||||
var people : Array <Person>
|
||||
|
||||
init {
|
||||
this.people = people
|
||||
}
|
||||
|
||||
fun readFrom (input: CodedInputStream) {
|
||||
readFromNoTag(input)
|
||||
}
|
||||
|
||||
fun readFromNoTag (input: CodedInputStream) {
|
||||
if (people.size > 0) {
|
||||
val tag = input.readTag()
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
people[i - 1].mergeFrom(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun build(): AddressBook {
|
||||
return AddressBook(people)
|
||||
}
|
||||
}
|
||||
|
||||
fun mergeFrom (input: CodedInputStream) {
|
||||
if (people.size > 0) {
|
||||
val tag = input.readTag()
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
people[i - 1].mergeFrom(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,14 +24,22 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val actualWireType = WireFormat.getTagWireType(tag)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.VARINT, actualWireType)
|
||||
return readRawVarint32()
|
||||
return readInt32NoTag()
|
||||
}
|
||||
|
||||
// Note that unsigned integer types are stored as their signed counterparts with top bit
|
||||
// simply stored in the sign bit - similar to Java's protobuf implementation. Hence, all
|
||||
// methods reading unsigned ints simply redirect call to corresponding signed-reading method
|
||||
fun readUInt32(expectedFieldNumber: Int): Int {
|
||||
return readInt32(expectedFieldNumber)
|
||||
val tag = readTag()
|
||||
val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val actualWireType = WireFormat.getTagWireType(tag)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.VARINT, actualWireType)
|
||||
return readUInt32NoTag()
|
||||
}
|
||||
|
||||
fun readUInt32NoTag(): Int {
|
||||
return readInt32NoTag()
|
||||
}
|
||||
|
||||
fun readInt64(expectedFieldNumber: Int): Long {
|
||||
@@ -39,12 +47,20 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val actualWireType = WireFormat.getTagWireType(tag)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, actualWireType, WireType.VARINT)
|
||||
return readRawVarint64()
|
||||
return readInt64NoTag()
|
||||
}
|
||||
|
||||
// See note on unsigned integers implementations above
|
||||
fun readUInt64(expectedFieldNumber: Int): Long {
|
||||
return readUInt64(expectedFieldNumber)
|
||||
val tag = readTag()
|
||||
val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val actualWireType = WireFormat.getTagWireType(tag)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, actualWireType, WireType.VARINT)
|
||||
return readUInt64NoTag()
|
||||
}
|
||||
|
||||
fun readUInt64NoTag(): Long {
|
||||
return readInt64NoTag()
|
||||
}
|
||||
|
||||
fun readBool(expectedFieldNumber: Int): Boolean {
|
||||
@@ -52,7 +68,11 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val actualWireType = WireFormat.getTagWireType(tag)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, actualWireType, WireType.VARINT)
|
||||
val readValue = readRawVarint32()
|
||||
return readBoolNoTag()
|
||||
}
|
||||
|
||||
fun readBoolNoTag(): Boolean {
|
||||
val readValue = readInt32NoTag()
|
||||
val boolValue = when (readValue) {
|
||||
0 -> false
|
||||
1 -> true
|
||||
@@ -63,7 +83,15 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
|
||||
// Reading enums is like reading one int32 number. Caller is responsible for converting this ordinal to enum-object
|
||||
fun readEnum(expectedFieldNumber: Int): Int {
|
||||
return readInt32(expectedFieldNumber)
|
||||
val tag = readTag()
|
||||
val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val actualWireType = WireFormat.getTagWireType(tag)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.VARINT, actualWireType)
|
||||
return readEnumNoTag()
|
||||
}
|
||||
|
||||
fun readEnumNoTag(): Int {
|
||||
return readUInt32NoTag()
|
||||
}
|
||||
|
||||
fun readSInt32(expectedFieldNumber: Int): Int {
|
||||
@@ -71,7 +99,11 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val actualWireType = WireFormat.getTagWireType(tag)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.VARINT, actualWireType)
|
||||
return readZigZag32()
|
||||
return readSInt32NoTag()
|
||||
}
|
||||
|
||||
fun readSInt32NoTag(): Int {
|
||||
return readZigZag32NoTag()
|
||||
}
|
||||
|
||||
fun readSInt64(expectedFieldNumber: Int): Long {
|
||||
@@ -79,7 +111,7 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val actualWireType = WireFormat.getTagWireType(tag)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.VARINT, actualWireType)
|
||||
return readZigZag64()
|
||||
return readZigZag64NoTag()
|
||||
}
|
||||
|
||||
fun readFixed32(expectedFieldNumber: Int): Int {
|
||||
@@ -87,11 +119,23 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val actualWireType = WireFormat.getTagWireType(tag)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.FIX_32, actualWireType)
|
||||
return readFixed32NoInt()
|
||||
}
|
||||
|
||||
fun readFixed32NoInt(): Int {
|
||||
return readLittleEndianInt()
|
||||
}
|
||||
|
||||
fun readSFixed32(expectedFieldNumber: Int): Int {
|
||||
return readFixed32(expectedFieldNumber)
|
||||
val tag = readTag()
|
||||
val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val actualWireType = WireFormat.getTagWireType(tag)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.FIX_32, actualWireType)
|
||||
return readSFixed32NoTag()
|
||||
}
|
||||
|
||||
fun readSFixed32NoTag(): Int {
|
||||
return readLittleEndianInt()
|
||||
}
|
||||
|
||||
fun readFixed64(expectedFieldNumber: Int): Long {
|
||||
@@ -99,11 +143,23 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val actualWireType = WireFormat.getTagWireType(tag)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.FIX_64, actualWireType)
|
||||
return readFixed64NoTag()
|
||||
}
|
||||
|
||||
fun readFixed64NoTag(): Long {
|
||||
return readLittleEndianLong()
|
||||
}
|
||||
|
||||
fun readSFixed64(expectedFieldNumber: Int): Long {
|
||||
return readFixed64(expectedFieldNumber)
|
||||
val tag = readTag()
|
||||
val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val actualWireType = WireFormat.getTagWireType(tag)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.FIX_64, actualWireType)
|
||||
return readSFixed64NoTag()
|
||||
}
|
||||
|
||||
fun readSFixed64NoTag(): Long {
|
||||
return readLittleEndianLong()
|
||||
}
|
||||
|
||||
fun readDouble(expectedFieldNumber: Int): Double {
|
||||
@@ -111,6 +167,10 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val actualWireType = WireFormat.getTagWireType(tag)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.FIX_64, actualWireType)
|
||||
return readDoubleNoTag()
|
||||
}
|
||||
|
||||
fun readDoubleNoTag(): Double {
|
||||
return readLittleEndianDouble()
|
||||
}
|
||||
|
||||
@@ -119,6 +179,10 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val actualWireType = WireFormat.getTagWireType(tag)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.FIX_32, actualWireType)
|
||||
return readFloatNoTag()
|
||||
}
|
||||
|
||||
fun readFloatNoTag(): Float {
|
||||
return readLittleEndianFloat()
|
||||
}
|
||||
|
||||
@@ -127,11 +191,14 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
|
||||
val actualWireType = WireFormat.getTagWireType(tag)
|
||||
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.LENGTH_DELIMITED, actualWireType)
|
||||
val length = readRawVarint32()
|
||||
return readStringNoTag()
|
||||
}
|
||||
|
||||
fun readStringNoTag(): String {
|
||||
val length = readInt32NoTag()
|
||||
val value = String(readRawBytes(length))
|
||||
return value
|
||||
}
|
||||
|
||||
/** ============ Utility methods ==================
|
||||
* They are left non-private for cases when one wants to implement her/his own protocol format.
|
||||
* Then she/he can re-use low-level methods for operating with raw values, that are not annotated with Protobuf tags.
|
||||
@@ -191,7 +258,7 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
if (isAtEnd()) {
|
||||
return 0 // we can safely return 0 as sign of end of message, because 0-tags are illegal
|
||||
}
|
||||
val tag = readRawVarint32()
|
||||
val tag = readInt32NoTag()
|
||||
if (tag == 0) { // if we somehow had read 0-tag, then message is corrupted
|
||||
throw InvalidProtocolBufferException("Invalid tag 0")
|
||||
}
|
||||
@@ -199,7 +266,7 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
}
|
||||
|
||||
// reads varint not larger than 32-bit integer according to protobuf varint-encoding
|
||||
fun readRawVarint32(): Int {
|
||||
fun readInt32NoTag(): Int {
|
||||
var done: Boolean = false
|
||||
var result: Int = 0
|
||||
var step: Int = 0
|
||||
@@ -207,10 +274,10 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
val byte: Int = bufferedInput.read()
|
||||
result = result or
|
||||
(
|
||||
(byte and VARINT_INFO_BITS_MASK)
|
||||
shl
|
||||
(VARINT_INFO_BITS_COUNT * step)
|
||||
)
|
||||
(byte and VARINT_INFO_BITS_MASK)
|
||||
shl
|
||||
(VARINT_INFO_BITS_COUNT * step)
|
||||
)
|
||||
step++
|
||||
if ((byte and VARINT_UTIL_BIT_MASK) == 0) {
|
||||
done = true
|
||||
@@ -220,7 +287,7 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
}
|
||||
|
||||
// reads varint not larger than 64-bit integer according to protobuf varint-encoding
|
||||
fun readRawVarint64(): Long {
|
||||
fun readInt64NoTag(): Long {
|
||||
var done: Boolean = false
|
||||
var result: Long = 0
|
||||
var step: Int = 0
|
||||
@@ -228,10 +295,10 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
val byte: Int = bufferedInput.read()
|
||||
result = result or
|
||||
(
|
||||
(byte and VARINT_INFO_BITS_MASK).toLong()
|
||||
shl
|
||||
(VARINT_INFO_BITS_COUNT * step)
|
||||
)
|
||||
(byte and VARINT_INFO_BITS_MASK).toLong()
|
||||
shl
|
||||
(VARINT_INFO_BITS_COUNT * step)
|
||||
)
|
||||
step++
|
||||
if ((byte and VARINT_UTIL_BIT_MASK) == 0 || byte == -1) {
|
||||
done = true
|
||||
@@ -241,14 +308,14 @@ class CodedInputStream(input: java.io.InputStream) {
|
||||
}
|
||||
|
||||
// reads zig-zag encoded integer not larger than 32-bit long
|
||||
fun readZigZag32(): Int {
|
||||
val value = readRawVarint32()
|
||||
fun readZigZag32NoTag(): Int {
|
||||
val value = readInt32NoTag()
|
||||
return (value shr 1) xor (-(value and 1)) // bit magic for decoding zig-zag number
|
||||
}
|
||||
|
||||
// reads zig-zag encoded integer not larger than 64-bit long
|
||||
fun readZigZag64(): Long {
|
||||
val value = readRawVarint64()
|
||||
fun readZigZag64NoTag(): Long {
|
||||
val value = readInt64NoTag()
|
||||
return (value shr 1) xor (-(value and 1L)) // bit magic for decoding zig-zag number
|
||||
}
|
||||
|
||||
|
||||
@@ -8,114 +8,184 @@ import java.nio.ByteOrder
|
||||
class CodedOutputStream(val output: java.io.OutputStream) {
|
||||
fun writeTag(fieldNumber: Int, type: WireType) {
|
||||
val tag = (fieldNumber shl 3) or type.ordinal
|
||||
writeVarint32(tag)
|
||||
writeInt32NoTag(tag)
|
||||
}
|
||||
|
||||
fun writeInt32(fieldNumber: Int, value: Int) {
|
||||
fun writeInt32(fieldNumber: Int, value: Int?) {
|
||||
value ?: return
|
||||
writeTag(fieldNumber, WireType.VARINT)
|
||||
writeVarint32(value)
|
||||
writeInt32NoTag(value)
|
||||
}
|
||||
|
||||
// Note that unsigned integer types are stored as their signed counterparts with top bit
|
||||
// simply stored in the sign bit - similar to Java's protobuf implementation. Hence, all
|
||||
// methods, writing unsigned ints simply redirect call to corresponding signed-writing method
|
||||
fun writeUInt32(fieldNumber: Int, value: Int) {
|
||||
fun writeUInt32(fieldNumber: Int, value: Int?) {
|
||||
value ?: return
|
||||
writeInt32(fieldNumber, value)
|
||||
}
|
||||
|
||||
fun writeInt64(fieldNumber: Int, value: Long) {
|
||||
fun writeInt64(fieldNumber: Int, value: Long?) {
|
||||
value ?: return
|
||||
writeTag(fieldNumber, WireType.VARINT)
|
||||
writeVarint64(value)
|
||||
writeInt64NoTag(value)
|
||||
}
|
||||
|
||||
// See notes on unsigned integers implementation above
|
||||
fun writeUIn64(fieldNumber: Int, value: Long) {
|
||||
fun writeUInt64(fieldNumber: Int, value: Long?) {
|
||||
value ?: return
|
||||
writeInt64(fieldNumber, value)
|
||||
}
|
||||
|
||||
fun writeBool(fieldNumber: Int, value: Boolean) {
|
||||
writeInt32(fieldNumber, if (value) 1 else 0)
|
||||
fun writeBool(fieldNumber: Int, value: Boolean?) {
|
||||
value ?: return
|
||||
writeTag(fieldNumber, WireType.VARINT)
|
||||
writeBoolNoTag(value)
|
||||
}
|
||||
|
||||
fun writeBoolNoTag(value: Boolean) {
|
||||
writeInt32NoTag(if (value) 1 else 0)
|
||||
}
|
||||
|
||||
// Writing enums is like writing one int32 number. Caller is responsible for converting enum-object to ordinal
|
||||
fun writeEnum(fieldNumber: Int, value: Int) {
|
||||
writeInt32(fieldNumber, value)
|
||||
fun writeEnum(fieldNumber: Int, value: Int?) {
|
||||
value ?: return
|
||||
writeTag(fieldNumber, WireType.VARINT)
|
||||
writeEnumNoTag(value)
|
||||
}
|
||||
|
||||
fun writeSInt32(fieldNumber: Int, value: Int) {
|
||||
writeInt32(fieldNumber, (value shl 1) xor (value shr 31))
|
||||
fun writeEnumNoTag(value: Int) {
|
||||
writeInt32NoTag(value)
|
||||
}
|
||||
|
||||
fun writeSInt64(fieldNumber: Int, value: Long) {
|
||||
writeInt64(fieldNumber, (value shl 1) xor (value shr 31))
|
||||
fun writeSInt32(fieldNumber: Int, value: Int?) {
|
||||
value ?: return
|
||||
writeTag(fieldNumber, WireType.VARINT)
|
||||
writeSInt32NoTag(value)
|
||||
}
|
||||
|
||||
fun writeFixed32(fieldNumber: Int, value: Int) {
|
||||
fun writeSInt32NoTag(value: Int) {
|
||||
writeInt32NoTag((value shl 1) xor (value shr 31))
|
||||
}
|
||||
|
||||
fun writeSInt64(fieldNumber: Int, value: Long?) {
|
||||
value ?: return
|
||||
writeTag(fieldNumber, WireType.VARINT)
|
||||
writeSInt64NoTag(value)
|
||||
}
|
||||
|
||||
fun writeSInt64NoTag(value: Long) {
|
||||
writeInt64NoTag((value shl 1) xor (value shr 31))
|
||||
}
|
||||
|
||||
fun writeFixed32(fieldNumber: Int, value: Int?) {
|
||||
value ?: return
|
||||
writeTag(fieldNumber, WireType.FIX_32)
|
||||
writeFixed32NoTag(value)
|
||||
}
|
||||
|
||||
fun writeFixed32NoTag(value: Int) {
|
||||
writeLittleEndian(value)
|
||||
}
|
||||
|
||||
// See notes on unsigned integers implementation above
|
||||
fun writeSFixed32(fieldNumber: Int, value: Int) {
|
||||
writeFixed32(fieldNumber, value)
|
||||
fun writeSFixed32(fieldNumber: Int, value: Int?) {
|
||||
value ?: return
|
||||
writeTag(fieldNumber, WireType.FIX_32)
|
||||
writeSFixed32NoTag(value)
|
||||
}
|
||||
|
||||
fun writeFixed64(fieldNumber: Int, value: Long) {
|
||||
fun writeSFixed32NoTag(value: Int) {
|
||||
writeLittleEndian(value)
|
||||
}
|
||||
|
||||
fun writeFixed64(fieldNumber: Int, value: Long?) {
|
||||
value ?: return
|
||||
writeTag(fieldNumber, WireType.FIX_64)
|
||||
writeFixed64NoTag(value)
|
||||
}
|
||||
|
||||
fun writeFixed64NoTag(value: Long) {
|
||||
writeLittleEndian(value)
|
||||
}
|
||||
|
||||
// See notes on unsigned integers implementation above
|
||||
fun writeSFixed64(fieldNumber: Int, value: Long) {
|
||||
writeFixed64(fieldNumber, value)
|
||||
}
|
||||
|
||||
fun writeDouble(fieldNumber: Int, value: Double) {
|
||||
fun writeSFixed64(fieldNumber: Int, value: Long?) {
|
||||
value ?: return
|
||||
writeTag(fieldNumber, WireType.FIX_64)
|
||||
writeSFixed64NoTag(value)
|
||||
}
|
||||
|
||||
fun writeSFixed64NoTag(value: Long) {
|
||||
writeLittleEndian(value)
|
||||
}
|
||||
|
||||
fun writeFloat(fieldNumber: Int, value: Float) {
|
||||
fun writeDouble(fieldNumber: Int, value: Double?) {
|
||||
value ?: return
|
||||
writeTag(fieldNumber, WireType.FIX_64)
|
||||
writeDoubleNoTag(value)
|
||||
}
|
||||
|
||||
fun writeDoubleNoTag(value: Double) {
|
||||
writeLittleEndian(value)
|
||||
}
|
||||
|
||||
fun writeFloat(fieldNumber: Int, value: Float?) {
|
||||
value ?: return
|
||||
writeTag(fieldNumber, WireType.FIX_32)
|
||||
writeFloatNoTag(value)
|
||||
}
|
||||
|
||||
fun writeFloatNoTag(value: Float) {
|
||||
writeLittleEndian(value)
|
||||
}
|
||||
|
||||
fun writeString(fieldNumber: Int, value: String) {
|
||||
fun writeString(fieldNumber: Int, value: String?) {
|
||||
value ?: return
|
||||
writeTag(fieldNumber, WireType.LENGTH_DELIMITED)
|
||||
writeVarint32(value.length)
|
||||
writeStringNoTag(value)
|
||||
}
|
||||
|
||||
fun writeStringNoTag(value: String) {
|
||||
writeInt32NoTag(value.length)
|
||||
output.write(value.toByteArray())
|
||||
}
|
||||
|
||||
fun writeLittleEndian(value: Int) {
|
||||
fun writeLittleEndian(value: Int?) {
|
||||
value ?: return
|
||||
val bb = ByteBuffer.allocate(4)
|
||||
bb.order(ByteOrder.LITTLE_ENDIAN)
|
||||
bb.putInt(value)
|
||||
output.write(bb.array())
|
||||
}
|
||||
|
||||
fun writeLittleEndian(value: Long) {
|
||||
fun writeLittleEndian(value: Long?) {
|
||||
value ?: return
|
||||
val bb = ByteBuffer.allocate(8)
|
||||
bb.order(ByteOrder.LITTLE_ENDIAN)
|
||||
bb.putLong(value)
|
||||
output.write(bb.array())
|
||||
}
|
||||
|
||||
fun writeLittleEndian(value: Double) {
|
||||
fun writeLittleEndian(value: Double?) {
|
||||
value ?: return
|
||||
val bb = ByteBuffer.allocate(8)
|
||||
bb.order(ByteOrder.LITTLE_ENDIAN)
|
||||
bb.putDouble(value)
|
||||
output.write(bb.array())
|
||||
}
|
||||
|
||||
fun writeLittleEndian(value: Float) {
|
||||
fun writeLittleEndian(value: Float?) {
|
||||
value ?: return
|
||||
val bb = ByteBuffer.allocate(4)
|
||||
bb.order(ByteOrder.LITTLE_ENDIAN)
|
||||
bb.putFloat(value)
|
||||
output.write(bb.array())
|
||||
}
|
||||
|
||||
fun writeVarint32(value: Int) {
|
||||
var curValue = value
|
||||
fun writeInt32NoTag(value: Int?) {
|
||||
value ?: return
|
||||
var curValue: Int = value
|
||||
|
||||
// we have at most 32 information bits. With overhead of 1 bit per 7 bits we need at most 5 bytes for encoding
|
||||
val res = ByteArray(5)
|
||||
@@ -139,8 +209,9 @@ class CodedOutputStream(val output: java.io.OutputStream) {
|
||||
output.write(res, 0, resSize)
|
||||
}
|
||||
|
||||
fun writeVarint64(value: Long) {
|
||||
var curValue = value
|
||||
fun writeInt64NoTag(value: Long?) {
|
||||
value ?: return
|
||||
var curValue: Long = value
|
||||
|
||||
// we have at most 64 information bits. With overhead of 1 bit per 7 bits we need at most 10 bytes for encoding
|
||||
val res = ByteArray(10)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
interface Message {
|
||||
fun writeTo(output: CodedOutputStream)
|
||||
fun readFrom(input: CodedInputStream) : Message
|
||||
fun getBuilder() : Builder
|
||||
|
||||
//TODO: think about something similar to static method getDefaultInstance()
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
class Person constructor (name: kotlin.String?, id: Int?, email: kotlin.String?, phones: Array <PhoneNumber> ) {
|
||||
var name : kotlin.String?
|
||||
private set
|
||||
|
||||
var id : Int?
|
||||
private set
|
||||
|
||||
var email : kotlin.String?
|
||||
private set
|
||||
|
||||
var phones : Array <PhoneNumber>
|
||||
private set
|
||||
|
||||
|
||||
init {
|
||||
this.name = name
|
||||
this.id = id
|
||||
this.email = email
|
||||
this.phones = phones
|
||||
}
|
||||
enum class PhoneType(val ord: Int) {
|
||||
MOBILE (0),
|
||||
HOME (1),
|
||||
WORK (2);
|
||||
|
||||
companion object {
|
||||
fun fromIntToPhoneType (ord: Int): PhoneType {
|
||||
return when (ord) {
|
||||
0 -> PhoneType.MOBILE
|
||||
1 -> PhoneType.HOME
|
||||
2 -> PhoneType.WORK
|
||||
else -> throw InvalidProtocolBufferException("Error: got unexpected int ${ord} while parsing PhoneType ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
class PhoneNumber constructor (number: kotlin.String?, type: PhoneType?) {
|
||||
var number : kotlin.String?
|
||||
private set
|
||||
|
||||
var type : PhoneType?
|
||||
private set
|
||||
|
||||
|
||||
init {
|
||||
this.number = number
|
||||
this.type = type
|
||||
}
|
||||
|
||||
fun writeTo (output: CodedOutputStream) {
|
||||
writeToNoTag(output)
|
||||
}
|
||||
|
||||
fun writeToNoTag (output: CodedOutputStream) {
|
||||
output.writeString (1, number)
|
||||
output.writeEnum (2, type?.ord)
|
||||
}
|
||||
|
||||
class BuilderPhoneNumber constructor (number: kotlin.String?, type: PhoneType?) {
|
||||
var number : kotlin.String?
|
||||
|
||||
var type : PhoneType?
|
||||
|
||||
|
||||
init {
|
||||
this.number = number
|
||||
this.type = type
|
||||
}
|
||||
|
||||
fun readFrom (input: CodedInputStream) {
|
||||
readFromNoTag(input)
|
||||
}
|
||||
|
||||
fun readFromNoTag (input: CodedInputStream) {
|
||||
number = input.readString(1)
|
||||
type = PhoneType.fromIntToPhoneType(input.readEnum(2))
|
||||
}
|
||||
|
||||
fun build(): PhoneNumber {
|
||||
return PhoneNumber(number, type)
|
||||
}
|
||||
}
|
||||
|
||||
fun mergeFrom (input: CodedInputStream) {
|
||||
number = input.readString(1)
|
||||
type = PhoneType.fromIntToPhoneType(input.readEnum(2))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun writeTo (output: CodedOutputStream) {
|
||||
writeToNoTag(output)
|
||||
}
|
||||
|
||||
fun writeToNoTag (output: CodedOutputStream) {
|
||||
output.writeString (1, name)
|
||||
output.writeInt32 (2, id)
|
||||
output.writeString (3, email)
|
||||
if (phones.size > 0) {
|
||||
output.writeInt32NoTag(phones.size)
|
||||
for (item in phones) {
|
||||
item.writeToNoTag(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BuilderPerson constructor (name: kotlin.String?, id: Int?, email: kotlin.String?, phones: Array <PhoneNumber> ) {
|
||||
var name : kotlin.String?
|
||||
|
||||
var id : Int?
|
||||
|
||||
var email : kotlin.String?
|
||||
|
||||
var phones : Array <PhoneNumber>
|
||||
|
||||
|
||||
init {
|
||||
this.name = name
|
||||
this.id = id
|
||||
this.email = email
|
||||
this.phones = phones
|
||||
}
|
||||
|
||||
fun readFrom (input: CodedInputStream) {
|
||||
readFromNoTag(input)
|
||||
}
|
||||
|
||||
fun readFromNoTag (input: CodedInputStream) {
|
||||
name = input.readString(1)
|
||||
id = input.readInt32(2)
|
||||
email = input.readString(3)
|
||||
if (phones.size > 0) {
|
||||
val tag = input.readTag()
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
phones[i - 1].mergeFrom(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun build(): Person {
|
||||
return Person(name, id, email, phones)
|
||||
}
|
||||
}
|
||||
|
||||
fun mergeFrom (input: CodedInputStream) {
|
||||
name = input.readString(1)
|
||||
id = input.readInt32(2)
|
||||
email = input.readString(3)
|
||||
if (phones.size > 0) {
|
||||
val tag = input.readTag()
|
||||
val listSize = input.readInt32NoTag()
|
||||
for (i in 1..listSize) {
|
||||
phones[i - 1].mergeFrom(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,20 @@ import java.io.ByteArrayOutputStream
|
||||
fun testMessageSerialization() {
|
||||
val s = ByteArrayOutputStream()
|
||||
val outs = CodedOutputStream(s)
|
||||
val msg = PersonMessage(name = "John Doe", id = 42, hasCat = true)
|
||||
val msg = Person(
|
||||
name = "John Doe",
|
||||
id = 42,
|
||||
email = "wtf@dsada.com",
|
||||
phones = arrayOf (
|
||||
Person.PhoneNumber("8-800-555-35-35", Person.PhoneType.WORK),
|
||||
Person.PhoneNumber("228-322", Person.PhoneType.HOME)
|
||||
)
|
||||
)
|
||||
msg.writeTo(outs)
|
||||
|
||||
val ins = CodedInputStream(ByteArrayInputStream(s.toByteArray()))
|
||||
val readMsg = PersonMessage("", 0, false)
|
||||
readMsg.readFrom(ins)
|
||||
val readMsg = Person("", 0, "", arrayOf())
|
||||
readMsg.mergeFrom(ins)
|
||||
|
||||
assert(msg == readMsg)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user