--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# 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
+ 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