Supported nested messages, repeated fields in Proto compiler. Fixed Kotin runtime appropriately.

This commit is contained in:
dsavvinov
2016-07-13 19:40:59 +03:00
parent fe27c80648
commit cf4cb7556d
15 changed files with 3588 additions and 156 deletions
@@ -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)
}
}
}
}
@@ -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>
}
+62
View File
@@ -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)
}
}
}
}
+94 -27
View File
@@ -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
}
+106 -35
View File
@@ -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)
-1
View File
@@ -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()
+158
View File
@@ -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)
}
}
}
}
+11 -3
View File
@@ -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)
}