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 { namespace kotlin {
void ClassGenerator::generateCode(io::Printer *printer, bool isBuilder) const { void ClassGenerator::generateCode(io::Printer *printer, bool isBuilder) const {
// print class header generateHeader(printer, isBuilder);
map<string, string> vars;
vars["modifier"] = modifier.getName();
vars["name"] = (isBuilder? "Builder" : "") + simpleName;
printer->Print(vars,
"$modifier$ $name$ private constructor () {"
"\n"
);
printer->Indent(); printer->Indent();
// generate code for nested classes declarations /**
for (ClassGenerator *gen: classesDeclarations) { * Field generator should know if it is generating code for builder.
gen->generateCode(printer, isBuilder); * or for fair class to choose between 'val' and 'var'.
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
for (FieldGenerator *gen: properties) { for (FieldGenerator *gen: properties) {
gen->generateCode(printer); gen->generateCode(printer, isBuilder);
printer->Print("\n"); printer->Print("\n");
} }
// generate constructor for builders printer->Print("\n");
if (isBuilder) { generateInitSection(printer);
printer->Print("\n");
generateConstructor(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) { if (!isBuilder) {
printer->Print("\n"); printer->Print("\n");
generateBuilder(printer); generateBuilder(printer);
printer->Print("\n");
generateMergeFrom(printer);
}
// build() is only for builders
if (isBuilder) {
printer->Print("\n");
generateBuildMethod(printer);
} }
printer->Outdent(); 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 simpleName = descriptor->name(); // TODO: think about more careful class naming
modifier = ClassModifier(ClassModifier::CLASS);
int field_count = descriptor->field_count(); int field_count = descriptor->field_count();
for (int i = 0; i < field_count; ++i) { 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 { void ClassGenerator::generateMergeFrom(io::Printer * printer) const {
// generate header //TODO: Looks pretty dirty. Should reconsider process of generating readFrom, mergeFrom and writeTo.
printer->Print("private constructor(\n"); 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) { for (int i = 0; i < properties.size(); ++i) {
// generate argument definition properties[i]->generateSerializationCode(printer, /* isRead = */ true);
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");
} }
// 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) { for (int i = 0; i < properties.size(); ++i) {
map <string, string> vars; map <string, string> vars;
vars["name"] = properties[i]->simpleName; vars["name"] = properties[i]->simpleName;
printer->Print(vars, printer->Print(vars,
"this.$name$ = $name$" "this.$name$ = $name$"
"\n" "\n"
); );
} }
printer->Outdent(); printer->Outdent();
printer->Print("}"); printer->Print("}\n");
} }
@@ -32,7 +32,6 @@ public:
class ClassGenerator { class ClassGenerator {
public: public:
ClassModifier modifier;
string simpleName; string simpleName;
vector <FieldGenerator *> properties; vector <FieldGenerator *> properties;
vector <ClassGenerator *> classesDeclarations; vector <ClassGenerator *> classesDeclarations;
@@ -41,15 +40,30 @@ public:
ClassGenerator (Descriptor const * descriptor); ClassGenerator (Descriptor const * descriptor);
~ClassGenerator (); ~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 * 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 * 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.) * with very little differences (like changing 'val's to 'var's and etc.)
*/ */
void generateCode (io::Printer * printer, bool isBuilder = false) const; void generateHeader(io::Printer * printer, bool isBuilder = false) const;
private:
void generateBuilder (io::Printer * printer) const; /**
void generateConstructor(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 } // namespace kotlin
@@ -47,12 +47,58 @@ void EnumGenerator::generateCode(io::Printer * printer) const {
if (i + 1 != enumValues.size()) { if (i + 1 != enumValues.size()) {
printer->Print(","); printer->Print(",");
} }
else {
printer->Print(";"); // semicolon is necessary as companion object will follow
}
printer->Print("\n"); printer->Print("\n");
} }
printer->Print("\n");
gemerateEnumConverters(printer);
printer->Outdent(); printer->Outdent();
printer->Print("}"); 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() { EnumGenerator::~EnumGenerator() {
for (int i = 0; i < enumValues.size(); ++i) { for (int i = 0; i < enumValues.size(); ++i) {
delete enumValues[i]; delete enumValues[i];
@@ -30,6 +30,9 @@ public:
vector <EnumValueGenerator *> enumValues; vector <EnumValueGenerator *> enumValues;
void generateCode(io::Printer *) const; void generateCode(io::Printer *) const;
private:
void gemerateEnumConverters(io::Printer *printer) const;
}; };
} // namespace kotlin } // namespace kotlin
@@ -28,7 +28,7 @@ string FieldGenerator::protobufToKotlinField() const {
postamble = "?"; postamble = "?";
break; break;
case FieldDescriptor::LABEL_REPEATED: case FieldDescriptor::LABEL_REPEATED:
preamble = "List <"; preamble = "Array <";
postamble = "> "; postamble = "> ";
break; break;
} }
@@ -90,16 +90,18 @@ string FieldGenerator::getInitValue() const {
return fieldName + "()"; return fieldName + "()";
} }
void FieldGenerator::generateCode(io::Printer *printer) const { void FieldGenerator::generateCode(io::Printer *printer, bool isBuilder) 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.
*/
map<string, string> vars; map<string, string> vars;
vars["name"] = simpleName; vars["name"] = simpleName;
vars["field"] = protobufToKotlinField(); vars["field"] = protobufToKotlinField();
vars["initValue"] = initValue; printer->Print(vars, "var $name$ : $field$\n");
printer->Print(vars, "val $name$ : $field$ = $initValue$");
// make setter private for fair classes
if (!isBuilder) {
printer->Indent();
printer->Print("private set\n");
printer->Outdent();
}
} }
FieldGenerator::FieldGenerator(FieldDescriptor const * descriptor) FieldGenerator::FieldGenerator(FieldDescriptor const * descriptor)
@@ -107,9 +109,159 @@ FieldGenerator::FieldGenerator(FieldDescriptor const * descriptor)
, modifier(descriptor->label()) , modifier(descriptor->label())
, simpleName(descriptor->name()) , simpleName(descriptor->name())
, fieldName(protobufToKotlinField()) , fieldName(protobufToKotlinField())
, fieldType(protobufToKotlinType())
, initValue(getInitValue()) , 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 } // namespace kotlin
} // namspace compiler } // namspace compiler
} // namespace protobuf } // namespace protobuf
@@ -21,13 +21,24 @@ private:
string protobufToKotlinType () const; string protobufToKotlinType () const;
string getInitValue() 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: public:
FieldDescriptor::Label modifier; FieldDescriptor::Label modifier;
string simpleName; string simpleName;
string fieldName; string fieldName;
string fieldType;
string initValue; string initValue;
void generateCode(io::Printer * printer, bool isBuilder = false) const;
void generateCode(io::Printer *) const; void generateSerializationCode(io::Printer * printer, bool isRead = false, bool noTag = false) const;
FieldGenerator(FieldDescriptor const * descriptor); FieldGenerator(FieldDescriptor const * descriptor);
}; };
@@ -1,25 +1,221 @@
class Person private constructor () { class Person private constructor (name: kotlin.String?, id: Int?, email: kotlin.String?, phones: Array <PhoneNumber> ) {
class PhoneNumber private constructor () { var name : kotlin.String?
val number : kotlin.String? = null private set
val type : PhoneType? = null
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) { enum class PhoneType(val ord: Int) {
MOBILE (0), MOBILE (0),
HOME (1), 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 actualFieldNumber = WireFormat.getTagFieldNumber(tag)
val actualWireType = WireFormat.getTagWireType(tag) val actualWireType = WireFormat.getTagWireType(tag)
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.VARINT, actualWireType) checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.VARINT, actualWireType)
return readRawVarint32() return readInt32NoTag()
} }
// Note that unsigned integer types are stored as their signed counterparts with top bit // 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 // 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 // methods reading unsigned ints simply redirect call to corresponding signed-reading method
fun readUInt32(expectedFieldNumber: Int): Int { 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 { fun readInt64(expectedFieldNumber: Int): Long {
@@ -39,12 +47,20 @@ class CodedInputStream(input: java.io.InputStream) {
val actualFieldNumber = WireFormat.getTagFieldNumber(tag) val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
val actualWireType = WireFormat.getTagWireType(tag) val actualWireType = WireFormat.getTagWireType(tag)
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, actualWireType, WireType.VARINT) checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, actualWireType, WireType.VARINT)
return readRawVarint64() return readInt64NoTag()
} }
// See note on unsigned integers implementations above // See note on unsigned integers implementations above
fun readUInt64(expectedFieldNumber: Int): Long { 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 { fun readBool(expectedFieldNumber: Int): Boolean {
@@ -52,7 +68,11 @@ class CodedInputStream(input: java.io.InputStream) {
val actualFieldNumber = WireFormat.getTagFieldNumber(tag) val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
val actualWireType = WireFormat.getTagWireType(tag) val actualWireType = WireFormat.getTagWireType(tag)
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, actualWireType, WireType.VARINT) checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, actualWireType, WireType.VARINT)
val readValue = readRawVarint32() return readBoolNoTag()
}
fun readBoolNoTag(): Boolean {
val readValue = readInt32NoTag()
val boolValue = when (readValue) { val boolValue = when (readValue) {
0 -> false 0 -> false
1 -> true 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 // Reading enums is like reading one int32 number. Caller is responsible for converting this ordinal to enum-object
fun readEnum(expectedFieldNumber: Int): Int { 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 { fun readSInt32(expectedFieldNumber: Int): Int {
@@ -71,7 +99,11 @@ class CodedInputStream(input: java.io.InputStream) {
val actualFieldNumber = WireFormat.getTagFieldNumber(tag) val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
val actualWireType = WireFormat.getTagWireType(tag) val actualWireType = WireFormat.getTagWireType(tag)
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.VARINT, actualWireType) checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.VARINT, actualWireType)
return readZigZag32() return readSInt32NoTag()
}
fun readSInt32NoTag(): Int {
return readZigZag32NoTag()
} }
fun readSInt64(expectedFieldNumber: Int): Long { fun readSInt64(expectedFieldNumber: Int): Long {
@@ -79,7 +111,7 @@ class CodedInputStream(input: java.io.InputStream) {
val actualFieldNumber = WireFormat.getTagFieldNumber(tag) val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
val actualWireType = WireFormat.getTagWireType(tag) val actualWireType = WireFormat.getTagWireType(tag)
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.VARINT, actualWireType) checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.VARINT, actualWireType)
return readZigZag64() return readZigZag64NoTag()
} }
fun readFixed32(expectedFieldNumber: Int): Int { fun readFixed32(expectedFieldNumber: Int): Int {
@@ -87,11 +119,23 @@ class CodedInputStream(input: java.io.InputStream) {
val actualFieldNumber = WireFormat.getTagFieldNumber(tag) val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
val actualWireType = WireFormat.getTagWireType(tag) val actualWireType = WireFormat.getTagWireType(tag)
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.FIX_32, actualWireType) checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.FIX_32, actualWireType)
return readFixed32NoInt()
}
fun readFixed32NoInt(): Int {
return readLittleEndianInt() return readLittleEndianInt()
} }
fun readSFixed32(expectedFieldNumber: Int): Int { 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 { fun readFixed64(expectedFieldNumber: Int): Long {
@@ -99,11 +143,23 @@ class CodedInputStream(input: java.io.InputStream) {
val actualFieldNumber = WireFormat.getTagFieldNumber(tag) val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
val actualWireType = WireFormat.getTagWireType(tag) val actualWireType = WireFormat.getTagWireType(tag)
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.FIX_64, actualWireType) checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.FIX_64, actualWireType)
return readFixed64NoTag()
}
fun readFixed64NoTag(): Long {
return readLittleEndianLong() return readLittleEndianLong()
} }
fun readSFixed64(expectedFieldNumber: Int): Long { 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 { fun readDouble(expectedFieldNumber: Int): Double {
@@ -111,6 +167,10 @@ class CodedInputStream(input: java.io.InputStream) {
val actualFieldNumber = WireFormat.getTagFieldNumber(tag) val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
val actualWireType = WireFormat.getTagWireType(tag) val actualWireType = WireFormat.getTagWireType(tag)
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.FIX_64, actualWireType) checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.FIX_64, actualWireType)
return readDoubleNoTag()
}
fun readDoubleNoTag(): Double {
return readLittleEndianDouble() return readLittleEndianDouble()
} }
@@ -119,6 +179,10 @@ class CodedInputStream(input: java.io.InputStream) {
val actualFieldNumber = WireFormat.getTagFieldNumber(tag) val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
val actualWireType = WireFormat.getTagWireType(tag) val actualWireType = WireFormat.getTagWireType(tag)
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.FIX_32, actualWireType) checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.FIX_32, actualWireType)
return readFloatNoTag()
}
fun readFloatNoTag(): Float {
return readLittleEndianFloat() return readLittleEndianFloat()
} }
@@ -127,11 +191,14 @@ class CodedInputStream(input: java.io.InputStream) {
val actualFieldNumber = WireFormat.getTagFieldNumber(tag) val actualFieldNumber = WireFormat.getTagFieldNumber(tag)
val actualWireType = WireFormat.getTagWireType(tag) val actualWireType = WireFormat.getTagWireType(tag)
checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.LENGTH_DELIMITED, actualWireType) checkFieldCorrectness(expectedFieldNumber, actualFieldNumber, WireType.LENGTH_DELIMITED, actualWireType)
val length = readRawVarint32() return readStringNoTag()
}
fun readStringNoTag(): String {
val length = readInt32NoTag()
val value = String(readRawBytes(length)) val value = String(readRawBytes(length))
return value return value
} }
/** ============ Utility methods ================== /** ============ Utility methods ==================
* They are left non-private for cases when one wants to implement her/his own protocol format. * 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. * 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()) { if (isAtEnd()) {
return 0 // we can safely return 0 as sign of end of message, because 0-tags are illegal 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 if (tag == 0) { // if we somehow had read 0-tag, then message is corrupted
throw InvalidProtocolBufferException("Invalid tag 0") 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 // reads varint not larger than 32-bit integer according to protobuf varint-encoding
fun readRawVarint32(): Int { fun readInt32NoTag(): Int {
var done: Boolean = false var done: Boolean = false
var result: Int = 0 var result: Int = 0
var step: Int = 0 var step: Int = 0
@@ -207,10 +274,10 @@ class CodedInputStream(input: java.io.InputStream) {
val byte: Int = bufferedInput.read() val byte: Int = bufferedInput.read()
result = result or result = result or
( (
(byte and VARINT_INFO_BITS_MASK) (byte and VARINT_INFO_BITS_MASK)
shl shl
(VARINT_INFO_BITS_COUNT * step) (VARINT_INFO_BITS_COUNT * step)
) )
step++ step++
if ((byte and VARINT_UTIL_BIT_MASK) == 0) { if ((byte and VARINT_UTIL_BIT_MASK) == 0) {
done = true 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 // reads varint not larger than 64-bit integer according to protobuf varint-encoding
fun readRawVarint64(): Long { fun readInt64NoTag(): Long {
var done: Boolean = false var done: Boolean = false
var result: Long = 0 var result: Long = 0
var step: Int = 0 var step: Int = 0
@@ -228,10 +295,10 @@ class CodedInputStream(input: java.io.InputStream) {
val byte: Int = bufferedInput.read() val byte: Int = bufferedInput.read()
result = result or result = result or
( (
(byte and VARINT_INFO_BITS_MASK).toLong() (byte and VARINT_INFO_BITS_MASK).toLong()
shl shl
(VARINT_INFO_BITS_COUNT * step) (VARINT_INFO_BITS_COUNT * step)
) )
step++ step++
if ((byte and VARINT_UTIL_BIT_MASK) == 0 || byte == -1) { if ((byte and VARINT_UTIL_BIT_MASK) == 0 || byte == -1) {
done = true done = true
@@ -241,14 +308,14 @@ class CodedInputStream(input: java.io.InputStream) {
} }
// reads zig-zag encoded integer not larger than 32-bit long // reads zig-zag encoded integer not larger than 32-bit long
fun readZigZag32(): Int { fun readZigZag32NoTag(): Int {
val value = readRawVarint32() val value = readInt32NoTag()
return (value shr 1) xor (-(value and 1)) // bit magic for decoding zig-zag number 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 // reads zig-zag encoded integer not larger than 64-bit long
fun readZigZag64(): Long { fun readZigZag64NoTag(): Long {
val value = readRawVarint64() val value = readInt64NoTag()
return (value shr 1) xor (-(value and 1L)) // bit magic for decoding zig-zag number 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) { class CodedOutputStream(val output: java.io.OutputStream) {
fun writeTag(fieldNumber: Int, type: WireType) { fun writeTag(fieldNumber: Int, type: WireType) {
val tag = (fieldNumber shl 3) or type.ordinal 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) writeTag(fieldNumber, WireType.VARINT)
writeVarint32(value) writeInt32NoTag(value)
} }
// Note that unsigned integer types are stored as their signed counterparts with top bit // 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 // 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 // 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) writeInt32(fieldNumber, value)
} }
fun writeInt64(fieldNumber: Int, value: Long) { fun writeInt64(fieldNumber: Int, value: Long?) {
value ?: return
writeTag(fieldNumber, WireType.VARINT) writeTag(fieldNumber, WireType.VARINT)
writeVarint64(value) writeInt64NoTag(value)
} }
// See notes on unsigned integers implementation above // See notes on unsigned integers implementation above
fun writeUIn64(fieldNumber: Int, value: Long) { fun writeUInt64(fieldNumber: Int, value: Long?) {
value ?: return
writeInt64(fieldNumber, value) writeInt64(fieldNumber, value)
} }
fun writeBool(fieldNumber: Int, value: Boolean) { fun writeBool(fieldNumber: Int, value: Boolean?) {
writeInt32(fieldNumber, if (value) 1 else 0) 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 // Writing enums is like writing one int32 number. Caller is responsible for converting enum-object to ordinal
fun writeEnum(fieldNumber: Int, value: Int) { fun writeEnum(fieldNumber: Int, value: Int?) {
writeInt32(fieldNumber, value) value ?: return
writeTag(fieldNumber, WireType.VARINT)
writeEnumNoTag(value)
} }
fun writeSInt32(fieldNumber: Int, value: Int) { fun writeEnumNoTag(value: Int) {
writeInt32(fieldNumber, (value shl 1) xor (value shr 31)) writeInt32NoTag(value)
} }
fun writeSInt64(fieldNumber: Int, value: Long) { fun writeSInt32(fieldNumber: Int, value: Int?) {
writeInt64(fieldNumber, (value shl 1) xor (value shr 31)) 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) writeTag(fieldNumber, WireType.FIX_32)
writeFixed32NoTag(value)
}
fun writeFixed32NoTag(value: Int) {
writeLittleEndian(value) writeLittleEndian(value)
} }
// See notes on unsigned integers implementation above // See notes on unsigned integers implementation above
fun writeSFixed32(fieldNumber: Int, value: Int) { fun writeSFixed32(fieldNumber: Int, value: Int?) {
writeFixed32(fieldNumber, value) 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) writeTag(fieldNumber, WireType.FIX_64)
writeFixed64NoTag(value)
}
fun writeFixed64NoTag(value: Long) {
writeLittleEndian(value) writeLittleEndian(value)
} }
// See notes on unsigned integers implementation above // See notes on unsigned integers implementation above
fun writeSFixed64(fieldNumber: Int, value: Long) { fun writeSFixed64(fieldNumber: Int, value: Long?) {
writeFixed64(fieldNumber, value) value ?: return
}
fun writeDouble(fieldNumber: Int, value: Double) {
writeTag(fieldNumber, WireType.FIX_64) writeTag(fieldNumber, WireType.FIX_64)
writeSFixed64NoTag(value)
}
fun writeSFixed64NoTag(value: Long) {
writeLittleEndian(value) 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) writeTag(fieldNumber, WireType.FIX_32)
writeFloatNoTag(value)
}
fun writeFloatNoTag(value: Float) {
writeLittleEndian(value) writeLittleEndian(value)
} }
fun writeString(fieldNumber: Int, value: String) { fun writeString(fieldNumber: Int, value: String?) {
value ?: return
writeTag(fieldNumber, WireType.LENGTH_DELIMITED) writeTag(fieldNumber, WireType.LENGTH_DELIMITED)
writeVarint32(value.length) writeStringNoTag(value)
}
fun writeStringNoTag(value: String) {
writeInt32NoTag(value.length)
output.write(value.toByteArray()) output.write(value.toByteArray())
} }
fun writeLittleEndian(value: Int) { fun writeLittleEndian(value: Int?) {
value ?: return
val bb = ByteBuffer.allocate(4) val bb = ByteBuffer.allocate(4)
bb.order(ByteOrder.LITTLE_ENDIAN) bb.order(ByteOrder.LITTLE_ENDIAN)
bb.putInt(value) bb.putInt(value)
output.write(bb.array()) output.write(bb.array())
} }
fun writeLittleEndian(value: Long) { fun writeLittleEndian(value: Long?) {
value ?: return
val bb = ByteBuffer.allocate(8) val bb = ByteBuffer.allocate(8)
bb.order(ByteOrder.LITTLE_ENDIAN) bb.order(ByteOrder.LITTLE_ENDIAN)
bb.putLong(value) bb.putLong(value)
output.write(bb.array()) output.write(bb.array())
} }
fun writeLittleEndian(value: Double) { fun writeLittleEndian(value: Double?) {
value ?: return
val bb = ByteBuffer.allocate(8) val bb = ByteBuffer.allocate(8)
bb.order(ByteOrder.LITTLE_ENDIAN) bb.order(ByteOrder.LITTLE_ENDIAN)
bb.putDouble(value) bb.putDouble(value)
output.write(bb.array()) output.write(bb.array())
} }
fun writeLittleEndian(value: Float) { fun writeLittleEndian(value: Float?) {
value ?: return
val bb = ByteBuffer.allocate(4) val bb = ByteBuffer.allocate(4)
bb.order(ByteOrder.LITTLE_ENDIAN) bb.order(ByteOrder.LITTLE_ENDIAN)
bb.putFloat(value) bb.putFloat(value)
output.write(bb.array()) output.write(bb.array())
} }
fun writeVarint32(value: Int) { fun writeInt32NoTag(value: Int?) {
var curValue = value 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 // 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) val res = ByteArray(5)
@@ -139,8 +209,9 @@ class CodedOutputStream(val output: java.io.OutputStream) {
output.write(res, 0, resSize) output.write(res, 0, resSize)
} }
fun writeVarint64(value: Long) { fun writeInt64NoTag(value: Long?) {
var curValue = value 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 // 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) val res = ByteArray(10)
-1
View File
@@ -4,7 +4,6 @@
interface Message { interface Message {
fun writeTo(output: CodedOutputStream) fun writeTo(output: CodedOutputStream)
fun readFrom(input: CodedInputStream) : Message
fun getBuilder() : Builder fun getBuilder() : Builder
//TODO: think about something similar to static method getDefaultInstance() //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() { fun testMessageSerialization() {
val s = ByteArrayOutputStream() val s = ByteArrayOutputStream()
val outs = CodedOutputStream(s) 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) msg.writeTo(outs)
val ins = CodedInputStream(ByteArrayInputStream(s.toByteArray())) val ins = CodedInputStream(ByteArrayInputStream(s.toByteArray()))
val readMsg = PersonMessage("", 0, false) val readMsg = Person("", 0, "", arrayOf())
readMsg.readFrom(ins) readMsg.mergeFrom(ins)
assert(msg == readMsg) assert(msg == readMsg)
} }