1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Serialize full Nit objects to MessagePack format
17 # There are 3 main entrypoint services:
18 # * `Writer::serialize_msgpack` adds an object to any stream writer.
19 # * `Serializable::serialize_msgpack` serializes the object to bytes.
20 # * `MsgPackSerializer` gives full control over the serialization of
21 # Nit objets to the MessagePack format.
22 module serialization_write
24 import serialization
::caching
25 private import serialization
::engine_tools
27 import serialization_common
31 # MessagePack deserialization engine
32 class MsgPackSerializer
33 super CachingSerializer
36 # Target writing stream
39 # Write plain MessagePack without metadata for deserialization?
41 # If `false`, the default, serialize to support deserialization:
43 # * Each object is encapsulated in an array that contains metadata and
44 # the actual object attributes in a map. The metadata includes the type
45 # name and references to already serialized object. This information
46 # supports deserializing the message, including cycles.
47 # * Preserve the Nit `Char` and `Byte` types as an object.
48 # * The generated MessagePack is standard and can be read by non-Nit programs.
49 # However, it contains some complexity that may make it harder to use.
51 # If `true`, serialize only the real data or non-Nit programs:
53 # * Nit objects are serialized to pure and standard MessagePack so they can
54 # be easily read by non-Nit programs.
55 # * Nit objects are serialized at every reference, so they may be duplicated.
56 # It is easier to read but it creates a larger output and it does not support
57 # cycles. Cyclic references are replaced by `null`.
58 # * The serialized data can only be deserialized to their expected static
59 # types, losing the knowledge of their dynamic type.
60 var plain_msgpack
= false is writable
62 # Should strings declaring the objects type and attributes name be cached?
64 # If `true` metadata strings are cached using `cache`.
65 # The first occurrence is written as an object declaration,
66 # successive occurrences are written as an object reference.
68 # If `false`, the default, metadata strings are written as pure MessagePack
69 # strings, without their own metadata.
71 # Using the cache may save some space by avoiding the repetition of
72 # names used by many types or attributes.
73 # However, it adds complexity to the generated message and may be less
74 # safe for versioning.
75 var cache_metadata_strings
= false is writable
77 # List of the current open objects, the first is the main target of the serialization
79 # Used only when `plain_msgpack == true` to detect cycles in serialization.
80 private var open_objects
= new Array[Object]
82 redef var current_object
= null
84 redef fun serialize
(object
)
86 if object
== null then
87 stream
.write_msgpack_null
90 for o
in open_objects
do
91 if object
.is_same_serialized
(o
) then
92 # Cycle, can't be managed in plain_msgpack mode
93 warn
"Cycle detected in serialized object, replacing reference with 'null'."
94 stream
.write_msgpack_null
99 open_objects
.add object
102 var last_object
= current_object
103 current_object
= object
104 object
.accept_msgpack_serializer
self
105 current_object
= last_object
107 if plain_msgpack
then open_objects
.pop
111 redef fun serialize_attribute
(name
, value
)
113 serialize_meta_string name
117 redef fun serialize_reference
(object
)
119 if not plain_msgpack
and cache
.has_object
(object
) then
120 # if already serialized, add local reference
121 var id
= cache
.id_for
(object
)
122 stream
.write_msgpack_ext
(ext_typ_ref
, id
.to_bytes
)
129 private fun serialize_meta_string
(type_name
: String)
131 if plain_msgpack
or not cache_metadata_strings
then
132 # String only version
133 stream
.write_msgpack_str type_name
137 if cache
.has_object
(type_name
) then
138 # if already serialized, add reference
139 var id
= cache
.id_for
(type_name
)
140 stream
.write_msgpack_ext
(ext_typ_ref
, id
.to_bytes
)
143 var id
= cache
.new_id_for
(type_name
)
144 stream
.write_msgpack_array
2 # obj+id, type_name
145 stream
.write_msgpack_ext
(ext_typ_obj
, id
.to_bytes
)
146 stream
.write_msgpack_str type_name
151 # Serialization visitor to count attribute in `Serializable` objects
152 class AttributeCounter
155 # Number of attributes counted
158 redef fun serialize_attribute
(name
, object
) do count
+= 1
162 # Services and serializables
165 # Serialize `value` in MessagePack format
166 fun serialize_msgpack
(value
: nullable Serializable, plain
: nullable Bool)
168 var serializer
= new MsgPackSerializer(self)
169 serializer
.plain_msgpack
= plain
or else false
170 serializer
.serialize value
174 redef class Serializable
176 # Serialize `self` to MessagePack bytes
178 # Set `plain = true` to generate standard MessagePack, without deserialization metadata.
179 # Use this option if the generated MessagePack will be read by non-Nit programs.
180 # Use the default, `plain = false`, if the MessagePack bytes are to be deserialized by a Nit program.
181 fun serialize_msgpack
(plain
: nullable Bool): Bytes
183 var stream
= new BytesWriter
184 stream
.serialize_msgpack
(self, plain
)
189 # Hook to customize the serialization of this class to MessagePack
191 # This method can be refined to customize the serialization by either
192 # writing pure JSON directly on the stream `v.stream` or
193 # by using other services of `MsgPackSerializer`.
195 # Most of the time, it is better to refine the method `core_serialize_to`
196 # which is used by all the serialization engines, not just MessagePack.
197 protected fun accept_msgpack_serializer
(v
: MsgPackSerializer)
200 # Count the number of attributes
201 var attribute_counter
= new AttributeCounter
202 accept_msgpack_attribute_counter attribute_counter
203 var n_attributes
= attribute_counter
.count
205 if not v
.plain_msgpack
then
208 if n_attributes
> 0 then n_meta_items
+= 1
209 n_meta_items
+= msgpack_extra_array_items
# obj+id, class_name, attributes
212 var id
= v
.cache
.new_id_for
(self)
213 v
.stream
.write_msgpack_array n_meta_items
214 v
.stream
.write_msgpack_ext
(v
.ext_typ_obj
, id
.to_bytes
)
215 v
.serialize_meta_string class_name
217 if n_attributes
> 0 then v
.stream
.write_msgpack_map n_attributes
219 v
.stream
.write_msgpack_map n_attributes
222 v
.serialize_core
self
225 # Hook to customize the behavior of the `AttributeCounter`
227 # By default, this method makes `v` visits all serializable attributes.
228 protected fun accept_msgpack_attribute_counter
(v
: AttributeCounter)
230 v
.serialize_core
self
233 # Hook to request a larger than usual metadata array
235 # Use by `SimpleCollection` and `Map` to append the items after
236 # the metadata and attributes.
237 protected fun msgpack_extra_array_items
: Int do return 0
240 redef class MsgPackExt
241 redef fun accept_msgpack_serializer
(v
) do v
.stream
.write_msgpack_ext
(typ
, data
)
245 redef fun accept_msgpack_serializer
(v
) do v
.stream
.write_msgpack_str
self
249 redef fun accept_msgpack_serializer
(v
) do v
.stream
.write_msgpack_int
self
253 redef fun accept_msgpack_serializer
(v
) do v
.stream
.write_msgpack_double
self
257 redef fun accept_msgpack_serializer
(v
) do v
.stream
.write_msgpack_bool
self
261 redef fun accept_msgpack_serializer
(v
)
263 if v
.plain_msgpack
then
265 v
.stream
.write_msgpack_int to_i
268 var bytes
= new Bytes.with_capacity
(1)
270 v
.stream
.write_msgpack_ext
(v
.ext_typ_byte
, bytes
)
276 redef fun accept_msgpack_serializer
(v
)
278 if v
.plain_msgpack
then
280 v
.stream
.write_msgpack_fixstr to_s
283 var bytes
= to_s
.to_bytes
284 v
.stream
.write_msgpack_ext
(v
.ext_typ_char
, bytes
)
290 redef fun accept_msgpack_serializer
(v
) do v
.stream
.write_msgpack_bin
self
294 redef fun accept_msgpack_serializer
(v
) do to_s
.accept_msgpack_serializer
(v
)
297 redef class SimpleCollection[E
]
298 redef fun accept_msgpack_serializer
(v
)
300 if not v
.plain_msgpack
then
301 # Add metadata and other attributes
306 v
.stream
.write_msgpack_array length
310 if not v
.try_to_serialize
(e
) then
311 assert e
!= null # null would have been serialized
312 v
.warn
"element of type {e.class_name} is not serializable."
313 v
.stream
.write_msgpack_null
318 redef fun msgpack_extra_array_items
do return 1
321 redef class Map[K
, V
]
322 redef fun accept_msgpack_serializer
(v
)
324 if not v
.plain_msgpack
then
325 # Add metadata and other attributes
330 v
.stream
.write_msgpack_map keys
.length
332 # Key / values, alternating
333 for key
, val
in self do
334 if not v
.try_to_serialize
(key
) then
335 assert val
!= null # null would have been serialized
336 v
.warn
"element of type {val.class_name} is not serializable."
337 v
.stream
.write_msgpack_null
340 if not v
.try_to_serialize
(val
) then
341 assert val
!= null # null would have been serialized
342 v
.warn
"element of type {val.class_name} is not serializable."
343 v
.stream
.write_msgpack_null
348 redef fun msgpack_extra_array_items
do return 1