Add support for protobuf API definitions
diff --git a/experiments/addressbook.proto b/experiments/addressbook.proto
new file mode 100644
index 0000000..bfdceea
--- /dev/null
+++ b/experiments/addressbook.proto
@@ -0,0 +1,33 @@
+// See README.txt for information and build instructions.
+
+syntax = "proto3";
+
+package tutorial;
+
+option java_package = "com.example.tutorial";
+option java_outer_classname = "AddressBookProtos";
+option csharp_namespace = "Google.Protobuf.Examples.AddressBook";
+
+message Person {
+  string name = 1;
+  int32 id = 2;        // Unique ID number for this person.
+  string email = 3;
+
+  enum PhoneType {
+    MOBILE = 0;
+    HOME = 1;
+    WORK = 2;
+  }
+
+  message PhoneNumber {
+    string number = 1;
+    PhoneType type = 2;
+  }
+
+  repeated PhoneNumber phones = 4;
+}
+
+// Our address book file is just one of these.
+message AddressBook {
+  repeated Person people = 1;
+}
diff --git a/experiments/addressbook_pb2.py b/experiments/addressbook_pb2.py
new file mode 100644
index 0000000..e545fb3
--- /dev/null
+++ b/experiments/addressbook_pb2.py
@@ -0,0 +1,206 @@
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: addressbook.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='addressbook.proto',
+  package='tutorial',
+  syntax='proto3',
+  serialized_pb=b'\n\x11\x61\x64\x64ressbook.proto\x12\x08tutorial\"\xd5\x01\n\x06Person\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\x05\x12\r\n\x05\x65mail\x18\x03 \x01(\t\x12,\n\x06phones\x18\x04 \x03(\x0b\x32\x1c.tutorial.Person.PhoneNumber\x1aG\n\x0bPhoneNumber\x12\x0e\n\x06number\x18\x01 \x01(\t\x12(\n\x04type\x18\x02 \x01(\x0e\x32\x1a.tutorial.Person.PhoneType\"+\n\tPhoneType\x12\n\n\x06MOBILE\x10\x00\x12\x08\n\x04HOME\x10\x01\x12\x08\n\x04WORK\x10\x02\"/\n\x0b\x41\x64\x64ressBook\x12 \n\x06people\x18\x01 \x03(\x0b\x32\x10.tutorial.PersonBP\n\x14\x63om.example.tutorialB\x11\x41\x64\x64ressBookProtos\xaa\x02$Google.Protobuf.Examples.AddressBookb\x06proto3'
+)
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+
+
+_PERSON_PHONETYPE = _descriptor.EnumDescriptor(
+  name='PhoneType',
+  full_name='tutorial.Person.PhoneType',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='MOBILE', index=0, number=0,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='HOME', index=1, number=1,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='WORK', index=2, number=2,
+      options=None,
+      type=None),
+  ],
+  containing_type=None,
+  options=None,
+  serialized_start=202,
+  serialized_end=245,
+)
+_sym_db.RegisterEnumDescriptor(_PERSON_PHONETYPE)
+
+
+_PERSON_PHONENUMBER = _descriptor.Descriptor(
+  name='PhoneNumber',
+  full_name='tutorial.Person.PhoneNumber',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='number', full_name='tutorial.Person.PhoneNumber.number', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='type', full_name='tutorial.Person.PhoneNumber.type', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=129,
+  serialized_end=200,
+)
+
+_PERSON = _descriptor.Descriptor(
+  name='Person',
+  full_name='tutorial.Person',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='tutorial.Person.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='id', full_name='tutorial.Person.id', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='email', full_name='tutorial.Person.email', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='phones', full_name='tutorial.Person.phones', index=3,
+      number=4, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[_PERSON_PHONENUMBER, ],
+  enum_types=[
+    _PERSON_PHONETYPE,
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=32,
+  serialized_end=245,
+)
+
+
+_ADDRESSBOOK = _descriptor.Descriptor(
+  name='AddressBook',
+  full_name='tutorial.AddressBook',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='people', full_name='tutorial.AddressBook.people', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=247,
+  serialized_end=294,
+)
+
+_PERSON_PHONENUMBER.fields_by_name['type'].enum_type = _PERSON_PHONETYPE
+_PERSON_PHONENUMBER.containing_type = _PERSON
+_PERSON.fields_by_name['phones'].message_type = _PERSON_PHONENUMBER
+_PERSON_PHONETYPE.containing_type = _PERSON
+_ADDRESSBOOK.fields_by_name['people'].message_type = _PERSON
+DESCRIPTOR.message_types_by_name['Person'] = _PERSON
+DESCRIPTOR.message_types_by_name['AddressBook'] = _ADDRESSBOOK
+
+Person = _reflection.GeneratedProtocolMessageType('Person', (_message.Message,), dict(
+
+  PhoneNumber = _reflection.GeneratedProtocolMessageType('PhoneNumber', (_message.Message,), dict(
+    DESCRIPTOR = _PERSON_PHONENUMBER,
+    __module__ = 'addressbook_pb2'
+    # @@protoc_insertion_point(class_scope:tutorial.Person.PhoneNumber)
+    ))
+  ,
+  DESCRIPTOR = _PERSON,
+  __module__ = 'addressbook_pb2'
+  # @@protoc_insertion_point(class_scope:tutorial.Person)
+  ))
+_sym_db.RegisterMessage(Person)
+_sym_db.RegisterMessage(Person.PhoneNumber)
+
+AddressBook = _reflection.GeneratedProtocolMessageType('AddressBook', (_message.Message,), dict(
+  DESCRIPTOR = _ADDRESSBOOK,
+  __module__ = 'addressbook_pb2'
+  # @@protoc_insertion_point(class_scope:tutorial.AddressBook)
+  ))
+_sym_db.RegisterMessage(AddressBook)
+
+
+DESCRIPTOR.has_options = True
+DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), b'\n\024com.example.tutorialB\021AddressBookProtos\252\002$Google.Protobuf.Examples.AddressBook')
+# @@protoc_insertion_point(module_scope)
diff --git a/experiments/encoing_test.py b/experiments/encoing_test.py
new file mode 100644
index 0000000..d7ddbfc
--- /dev/null
+++ b/experiments/encoing_test.py
@@ -0,0 +1,300 @@
+#!/usr/bin/env python
+
+"""
+We use this module to time-test various alternatives for building,
+serializing, and de-serializing objects.
+"""
+
+import time
+from copy import copy, deepcopy
+
+import addressbook_pb2
+from random import randint
+from uuid import uuid4
+from simplejson import dumps, loads, JSONEncoder
+
+
+class ProtoBufs(object):
+
+    FILENAME = 'addressbook.bin.pb'
+
+    @staticmethod
+    def makeAddressBook(n):
+
+        addressbook = addressbook_pb2.AddressBook()
+
+        for i in xrange(n):
+            person = addressbook.people.add()
+            person.id = i
+            person.name = uuid4().get_hex()
+            person.email = person.name + '@abc.com'
+
+            phone1 = person.phones.add()
+            phone1.number = str(randint(1000000000, 7999999999))
+            phone1.type = addressbook_pb2.Person.HOME
+
+            phone2 = person.phones.add()
+            phone2.number = str(randint(1000000000, 7999999999))
+            phone2.type = addressbook_pb2.Person.MOBILE
+
+        return addressbook
+
+    @staticmethod
+    def serialize(addressbook):
+        return addressbook.SerializeToString()
+
+    @staticmethod
+    def deserialize(str):
+        addressbook = addressbook_pb2.AddressBook()
+        addressbook.ParseFromString(str)
+        return addressbook
+
+    @staticmethod
+    def diff(addressbook1, addressbook2):
+        assert isinstance(addressbook1, addressbook_pb2.AddressBook)
+        assert isinstance(addressbook2, addressbook_pb2.AddressBook)
+        assert addressbook1 == addressbook2
+
+
+class JsonWithPythonData(object):
+
+    FILENAME = 'addressbook.native.json'
+
+    @staticmethod
+    def makeAddressBook(n):
+
+        addressbook = dict(people=[])
+        people = addressbook['people']
+
+        for i in xrange(n):
+            name = uuid4().get_hex()
+            people.append(dict(
+                id=i,
+                name=name,
+                email=name + '@abc.com',
+                phones=[
+                    dict(
+                        number=str(randint(1000000000, 7999999999)),
+                        type='HOME'
+                    ),
+                    dict(
+                        number=str(randint(1000000000, 7999999999)),
+                        type='MOBILE'
+                    )
+                ]
+            ))
+
+        return addressbook
+
+    @staticmethod
+    def serialize(addressbook):
+        return dumps(addressbook)
+
+    @staticmethod
+    def deserialize(str):
+        return loads(str)
+
+    @staticmethod
+    def diff(addressbook1, addressbook2):
+        assert isinstance(addressbook1, dict)
+        assert isinstance(addressbook2, dict)
+        assert addressbook1 == addressbook2
+
+
+class JsonWithPythonClasses(object):
+
+    class JsonEncoded(object):
+        def __eq__(self, other):
+            return self.__dict__ == other.__dict__
+
+    class Phone(JsonEncoded):
+
+        def __init__(self, number, type):
+            self.number = number
+            self.type = type
+
+        @property
+        def number(self):
+            return self._type
+        @number.setter
+        def number(self, number):
+            assert isinstance(number, str)
+            self._number = number
+
+        @property
+        def type(self):
+            return self._type
+        @number.setter
+        def type(self, type):
+            assert isinstance(type, str)
+            self._type = type
+
+    class Person(JsonEncoded):
+
+        def __init__(self, id, name, email, phones=list()):
+            self.id = id
+            self.name = name
+            self.email = email
+            self.phones = phones
+
+        @property
+        def id(self):
+            return self._id
+        @id.setter
+        def id(self, id):
+            assert isinstance(id, int)
+            self._id = id
+
+        @property
+        def name(self):
+            return self._name
+        @name.setter
+        def name(self, name):
+            assert isinstance(name, str)
+            self._name = name
+
+        @property
+        def email(self):
+            return self._email
+        @email.setter
+        def email(self, email):
+            assert isinstance(email, str)
+            self._email = email
+
+        @property
+        def phones(self):
+            return self._phones
+        @phones.setter
+        def phones(self, phones):
+            assert isinstance(phones, list)
+            self._phones = phones
+
+
+    class AddressBook(JsonEncoded):
+
+        def __init__(self, people=list()):
+            self.people = people
+
+        @property
+        def people(self):
+            return self._people
+        @people.setter
+        def people(self, people):
+            assert isinstance(people, list)
+            self._people = people
+
+    cls_map = {
+        Phone.__name__: Phone,
+        Person.__name__: Person,
+        AddressBook.__name__: AddressBook
+    }
+
+    class CustomEncoder(JSONEncoder):
+        def default(self, o):
+            if isinstance(o, JsonWithPythonClasses.JsonEncoded):
+                d = dict((k.strip('_'), v) for k, v in o.__dict__.iteritems())
+                d['_class'] = o.__class__.__name__
+                return d
+            return super(self).default(o)
+
+    @staticmethod
+    def as_object(dct):
+        if '_class' in dct and dct['_class'] in JsonWithPythonClasses.cls_map:
+            kw = deepcopy(dct)
+            cls_name = kw.pop('_class')
+            cls = JsonWithPythonClasses.cls_map[cls_name]
+            return cls(**kw)
+        return dct
+
+    FILENAME = 'addressbook.class.json'
+
+    @staticmethod
+    def makeAddressBook(n):
+
+        addressbook = JsonWithPythonClasses.AddressBook()
+        people = addressbook.people
+
+        for i in xrange(n):
+            name = uuid4().get_hex()
+            person = JsonWithPythonClasses.Person(
+                id=i,
+                name=name,
+                email=name + '@abc.com',
+                phones=[
+                    JsonWithPythonClasses.Phone(
+                        number=str(randint(1000000000, 7999999999)),
+                        type='HOME'
+                    ),
+                    JsonWithPythonClasses.Phone(
+                        number=str(randint(1000000000, 7999999999)),
+                        type='MOBILE'
+                    )
+                ]
+            )
+            people.append(person)
+
+        return addressbook
+
+    @staticmethod
+    def serialize(addressbook):
+        return dumps(addressbook, cls=JsonWithPythonClasses.CustomEncoder)
+
+    @staticmethod
+    def deserialize(str):
+        return loads(str, object_hook=JsonWithPythonClasses.as_object)
+
+    @staticmethod
+    def diff(addressbook1, addressbook2):
+        assert isinstance(addressbook1, JsonWithPythonClasses.AddressBook)
+        assert isinstance(addressbook2, JsonWithPythonClasses.AddressBook)
+        assert len(addressbook1.people) == len(addressbook2.people)
+        for i in xrange(len(addressbook1.people)):
+            assert addressbook1.people[i] == addressbook2.people[i], \
+                '\n%s\n!=\n%s' % (addressbook1.people[i].__dict__,
+                              addressbook2.people[i].__dict__)
+        assert addressbook1 == addressbook2
+
+
+def timetest(cls, n):
+
+    generator = cls()
+
+    # generate addressbook
+
+    t = time.time()
+    addressbook = generator.makeAddressBook(n)
+    t_make = time.time() - t
+
+    # serialize addressbook to string and save it to file
+    t = time.time()
+    str = generator.serialize(addressbook)
+    t_serialize = time.time() - t
+    size = len(str)
+
+    with open(cls.FILENAME, 'wb') as f:
+        f.write(str)
+
+    # deserialize by reading it back from file
+    t = time.time()
+    addressbook2 = generator.deserialize(str)
+    t_deserialize = time.time() - t
+
+    generator.diff(addressbook, addressbook2)
+
+    print "%-30s %12lf   %12lf   %12lf   %10d" % \
+          (cls.__name__,
+           1e6 * t_make / n,
+           1e6 * t_serialize / n,
+           1e6 * t_deserialize / n,
+           size / n)
+
+
+def run_tests(n):
+    print "%-30s %12s   %12s   %12s   %10s" % \
+          ('Method', 'Gen [us]', 'Ser [us]', 'Des [us]', 'Size [bytes]')
+    timetest(ProtoBufs, n)
+    timetest(JsonWithPythonData, n)
+    timetest(JsonWithPythonClasses, n)
+
+
+run_tests(10000)