Serialize full Nit objects to MessagePack format

There are 3 main entrypoint services:

  • Writer::serialize_msgpack adds an object to any stream writer.
  • Serializable::serialize_msgpack serializes the object to bytes.
  • MsgPackSerializer gives full control over the serialization of Nit objets to the MessagePack format.

Introduced classes

class AttributeCounter

msgpack :: AttributeCounter

Serialization visitor to count attribute in Serializable objects
class MsgPackSerializer

msgpack :: MsgPackSerializer

MessagePack deserialization engine

Redefined classes

redef enum Bool

msgpack :: serialization_write $ Bool

Native Booleans.
redef enum Byte

msgpack :: serialization_write $ Byte

Native bytes.
redef class Bytes

msgpack :: serialization_write $ Bytes

A buffer containing Byte-manipulation facilities
redef extern class CString

msgpack :: serialization_write $ CString

C string char *
redef enum Char

msgpack :: serialization_write $ Char

Native characters.
redef enum Float

msgpack :: serialization_write $ Float

Native floating point numbers.
redef enum Int

msgpack :: serialization_write $ Int

Native integer numbers.
redef interface Map[K: nullable Object, V: nullable Object]

msgpack :: serialization_write $ Map

Maps are associative collections: key -> item.
redef class MsgPackExt

msgpack :: serialization_write $ MsgPackExt

Application specific MessagePack extension
redef interface Serializable

msgpack :: serialization_write $ Serializable

Instances of this class can be passed to Serializer::serialize
redef interface SimpleCollection[E: nullable Object]

msgpack :: serialization_write $ SimpleCollection

Items can be added to these collections.
redef abstract class Text

msgpack :: serialization_write $ Text

High-level abstraction for all text representations
redef abstract class Writer

msgpack :: serialization_write $ Writer

A Stream that can be written to

All class definitions

class AttributeCounter

msgpack $ AttributeCounter

Serialization visitor to count attribute in Serializable objects
redef enum Bool

msgpack :: serialization_write $ Bool

Native Booleans.
redef enum Byte

msgpack :: serialization_write $ Byte

Native bytes.
redef class Bytes

msgpack :: serialization_write $ Bytes

A buffer containing Byte-manipulation facilities
redef extern class CString

msgpack :: serialization_write $ CString

C string char *
redef enum Char

msgpack :: serialization_write $ Char

Native characters.
redef enum Float

msgpack :: serialization_write $ Float

Native floating point numbers.
redef enum Int

msgpack :: serialization_write $ Int

Native integer numbers.
redef interface Map[K: nullable Object, V: nullable Object]

msgpack :: serialization_write $ Map

Maps are associative collections: key -> item.
redef class MsgPackExt

msgpack :: serialization_write $ MsgPackExt

Application specific MessagePack extension
class MsgPackSerializer

msgpack $ MsgPackSerializer

MessagePack deserialization engine
redef interface Serializable

msgpack :: serialization_write $ Serializable

Instances of this class can be passed to Serializer::serialize
redef interface SimpleCollection[E: nullable Object]

msgpack :: serialization_write $ SimpleCollection

Items can be added to these collections.
redef abstract class Text

msgpack :: serialization_write $ Text

High-level abstraction for all text representations
redef abstract class Writer

msgpack :: serialization_write $ Writer

A Stream that can be written to
package_diagram msgpack::serialization_write serialization_write msgpack::serialization_common serialization_common msgpack::serialization_write->msgpack::serialization_common msgpack::write write msgpack::serialization_write->msgpack::write msgpack::ext ext msgpack::serialization_write->msgpack::ext core core msgpack::serialization_common->core binary binary msgpack::write->binary serialization serialization msgpack::ext->serialization ...core ... ...core->core ...binary ... ...binary->binary ...serialization ... ...serialization->serialization msgpack::msgpack msgpack msgpack::msgpack->msgpack::serialization_write gamnit::common common gamnit::common->msgpack::msgpack gamnit::common... ... gamnit::common...->gamnit::common

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module array

core :: array

This module introduces the standard array structure.
module binary

binary :: binary

Read and write binary data with any Reader and Writer
module bitset

core :: bitset

Services to handle BitSet
module bytes

core :: bytes

Services for byte streams and arrays
module caching

serialization :: caching

Services for caching serialization engines
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.
module engine_tools

serialization :: engine_tools

Advanced services for serialization engines
module environ

core :: environ

Access to the environment variables of the process
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module file

core :: file

File manipulations (create, read, write, etc.)
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module inspect

serialization :: inspect

Refine Serializable::inspect to show more useful information
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module kernel

core :: kernel

Most basic classes and methods.
module list

core :: list

This module handle double linked lists
module math

core :: math

Mathematical operations
module meta

meta :: meta

Simple user-defined meta-level to manipulate types of instances as object.
module native

core :: native

Native structures for text and bytes
module numeric

core :: numeric

Advanced services for Numeric types
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module re

core :: re

Regular expression support for all services based on Pattern
module ropes

core :: ropes

Tree-based representation of a String.
module serialization

serialization :: serialization

General serialization services
module serialization_core

serialization :: serialization_core

Abstract services to serialize Nit objects to different formats
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module stream

core :: stream

Input and output streams of characters
module text

core :: text

All the classes and methods related to the manipulation of text entities
module time

core :: time

Management of time and dates
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O

Parents

module ext

msgpack :: ext

Application specific MessagePack extension MsgPackExt
module serialization_common

msgpack :: serialization_common

Serialization services for serialization_write and serialization_read
module write

msgpack :: write

Low-level write in MessagePack format to Writer streams

Children

module msgpack

msgpack :: msgpack

MessagePack, an efficient binary serialization format

Descendants

module a_star-m

a_star-m

module client

gamnit :: client

Client-side network services for games and such
module common

gamnit :: common

Services common to the client and server modules
module network

gamnit :: network

Easy client/server logic for games and simple distributed applications
module server

gamnit :: server

Server-side network services for games and such
# Serialize full Nit objects to MessagePack format
#
# There are 3 main entrypoint services:
# * `Writer::serialize_msgpack` adds an object to any stream writer.
# * `Serializable::serialize_msgpack` serializes the object to bytes.
# * `MsgPackSerializer` gives full control over the serialization of
#   Nit objets to the MessagePack format.
module serialization_write

import serialization::caching
private import serialization::engine_tools

import serialization_common
private import write
import ext

# MessagePack deserialization engine
class MsgPackSerializer
	super CachingSerializer
	super MsgPackEngine

	# Target writing stream
	var stream: Writer

	# Write plain MessagePack without metadata for deserialization?
	#
	# If `false`, the default, serialize to support deserialization:
	#
	# * Each object is encapsulated in an array that contains metadata and
	#   the actual object attributes in a map. The metadata includes the type
	#   name and references to already serialized object. This information
	#   supports deserializing the message, including cycles.
	# * Preserve the Nit `Char` and `Byte` types as an object.
	# * The generated MessagePack is standard and can be read by non-Nit programs.
	#   However, it contains some complexity that may make it harder to use.
	#
	# If `true`, serialize only the real data or non-Nit programs:
	#
	# * Nit objects are serialized to pure and standard MessagePack so they can
	#   be easily read by non-Nit programs.
	# * Nit objects are serialized at every reference, so they may be duplicated.
	#   It is easier to read but it creates a larger output and it does not support
	#   cycles. Cyclic references are replaced by `null`.
	# * The serialized data can only be deserialized to their expected static
	#   types, losing the knowledge of their dynamic type.
	var plain_msgpack = false is writable

	# Should strings declaring the objects type and attributes name be cached?
	#
	# If `true` metadata strings are cached using `cache`.
	# The first occurrence is written as an object declaration,
	# successive occurrences are written as an object reference.
	#
	# If `false`, the default, metadata strings are written as pure MessagePack
	# strings, without their own metadata.
	#
	# Using the cache may save some space by avoiding the repetition of
	# names used by many types or attributes.
	# However, it adds complexity to the generated message and may be less
	# safe for versioning.
	var cache_metadata_strings = false is writable

	# List of the current open objects, the first is the main target of the serialization
	#
	# Used only when `plain_msgpack == true` to detect cycles in serialization.
	private var open_objects = new Array[Object]

	redef var current_object = null

	redef fun serialize(object)
	do
		if object == null then
			stream.write_msgpack_null
		else
			if plain_msgpack then
				for o in open_objects do
					if object.is_same_serialized(o) then
						# Cycle, can't be managed in plain_msgpack mode
						warn "Cycle detected in serialized object, replacing reference with 'null'."
						stream.write_msgpack_null
						return
					end
				end

				open_objects.add object
			end

			var last_object = current_object
			current_object = object
			object.accept_msgpack_serializer self
			current_object = last_object

			if plain_msgpack then open_objects.pop
		end
	end

	redef fun serialize_attribute(name, value)
	do
		serialize_meta_string name
		super
	end

	redef fun serialize_reference(object)
	do
		if not plain_msgpack and cache.has_object(object) then
			# if already serialized, add local reference
			var id = cache.id_for(object)
			stream.write_msgpack_ext(ext_typ_ref, id.to_bytes)
		else
			# serialize
			serialize object
		end
	end

	private fun serialize_meta_string(type_name: String)
	do
		if plain_msgpack or not cache_metadata_strings then
			# String only version
			stream.write_msgpack_str type_name
			return
		end

		if cache.has_object(type_name) then
			# if already serialized, add reference
			var id = cache.id_for(type_name)
			stream.write_msgpack_ext(ext_typ_ref, id.to_bytes)
		else
			# serialize
			var id = cache.new_id_for(type_name)
			stream.write_msgpack_array 2 # obj+id, type_name
			stream.write_msgpack_ext(ext_typ_obj, id.to_bytes)
			stream.write_msgpack_str type_name
		end
	end
end

# Serialization visitor to count attribute in `Serializable` objects
class AttributeCounter
	super Serializer

	# Number of attributes counted
	var count = 0

	redef fun serialize_attribute(name, object) do count += 1
end

# ---
# Services and serializables

redef class Writer
	# Serialize `value` in MessagePack format
	fun serialize_msgpack(value: nullable Serializable, plain: nullable Bool)
	do
		var serializer = new MsgPackSerializer(self)
		serializer.plain_msgpack = plain or else false
		serializer.serialize value
	end
end

redef class Serializable

	# Serialize `self` to MessagePack bytes
	#
	# Set `plain = true` to generate standard MessagePack, without deserialization metadata.
	# Use this option if the generated MessagePack will be read by non-Nit programs.
	# Use the default, `plain = false`, if the MessagePack bytes are to be deserialized by a Nit program.
	fun serialize_msgpack(plain: nullable Bool): Bytes
	do
		var stream = new BytesWriter
		stream.serialize_msgpack(self, plain)
		stream.close
		return stream.bytes
	end

	# Hook to customize the serialization of this class to MessagePack
	#
	# This method can be refined to customize the serialization by either
	# writing pure JSON directly on the stream `v.stream` or
	# by using other services of `MsgPackSerializer`.
	#
	# Most of the time, it is better to refine the method `core_serialize_to`
	# which is used by all the serialization engines, not just MessagePack.
	protected fun accept_msgpack_serializer(v: MsgPackSerializer)
	do

		# Count the number of attributes
		var attribute_counter = new AttributeCounter
		accept_msgpack_attribute_counter attribute_counter
		var n_attributes = attribute_counter.count

		if not v.plain_msgpack then

			var n_meta_items = 2
			if n_attributes > 0 then n_meta_items += 1
			n_meta_items += msgpack_extra_array_items # obj+id, class_name, attributes

			# Metadata
			var id = v.cache.new_id_for(self)
			v.stream.write_msgpack_array n_meta_items
			v.stream.write_msgpack_ext(v.ext_typ_obj, id.to_bytes)
			v.serialize_meta_string class_name

			if n_attributes > 0 then v.stream.write_msgpack_map n_attributes
		else
			v.stream.write_msgpack_map n_attributes
		end

		v.serialize_core self
	end

	# Hook to customize the behavior of the `AttributeCounter`
	#
	# By default, this method makes `v` visits all serializable attributes.
	protected fun accept_msgpack_attribute_counter(v: AttributeCounter)
	do
		v.serialize_core self
	end

	# Hook to request a larger than usual metadata array
	#
	# Use by `SimpleCollection` and `Map` to append the items after
	# the metadata and attributes.
	protected fun msgpack_extra_array_items: Int do return 0
end

redef class MsgPackExt
	redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_ext(typ, data)
end

redef class Text
	redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_str self
end

redef class Int
	redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_int self
end

redef class Float
	redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_double self
end

redef class Bool
	redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_bool self
end

redef class Byte
	redef fun accept_msgpack_serializer(v)
	do
		if v.plain_msgpack then
			# Write as a string
			v.stream.write_msgpack_int to_i
		else
			# Write as ext
			var bytes = new Bytes.with_capacity(1)
			bytes.add self.to_i
			v.stream.write_msgpack_ext(v.ext_typ_byte, bytes)
		end
	end
end

redef class Char
	redef fun accept_msgpack_serializer(v)
	do
		if v.plain_msgpack then
			# Write as a string
			v.stream.write_msgpack_fixstr to_s
		else
			# Write as ext
			var bytes = to_s.to_bytes
			v.stream.write_msgpack_ext(v.ext_typ_char, bytes)
		end
	end
end

redef class Bytes
	redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_bin self
end

redef class CString
	redef fun accept_msgpack_serializer(v) do to_s.accept_msgpack_serializer(v)
end

redef class SimpleCollection[E]
	redef fun accept_msgpack_serializer(v)
	do
		if not v.plain_msgpack then
			# Add metadata and other attributes
			super
		end

		# Header
		v.stream.write_msgpack_array length

		# Items
		for e in self do
			if not v.try_to_serialize(e) then
				assert e != null # null would have been serialized
				v.warn "element of type {e.class_name} is not serializable."
				v.stream.write_msgpack_null
			end
		end
	end

	redef fun msgpack_extra_array_items do return 1
end

redef class Map[K, V]
	redef fun accept_msgpack_serializer(v)
	do
		if not v.plain_msgpack then
			# Add metadata and other attributes
			super
		end

		# Header
		v.stream.write_msgpack_map keys.length

		# Key / values, alternating
		for key, val in self do
			if not v.try_to_serialize(key) then
				assert val != null # null would have been serialized
				v.warn "element of type {val.class_name} is not serializable."
				v.stream.write_msgpack_null
			end

			if not v.try_to_serialize(val) then
				assert val != null # null would have been serialized
				v.warn "element of type {val.class_name} is not serializable."
				v.stream.write_msgpack_null
			end
		end
	end

	redef fun msgpack_extra_array_items do return 1
end
lib/msgpack/serialization_write.nit:15,1--349,3