Merge: nitunit: Use markdown2
[nit.git] / lib / msgpack / serialization_write.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 # Serialize full Nit objects to MessagePack format
16 #
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
23
24 import serialization::caching
25 private import serialization::engine_tools
26
27 import serialization_common
28 private import write
29 import ext
30
31 # MessagePack deserialization engine
32 class MsgPackSerializer
33 super CachingSerializer
34 super MsgPackEngine
35
36 # Target writing stream
37 var stream: Writer
38
39 # Write plain MessagePack without metadata for deserialization?
40 #
41 # If `false`, the default, serialize to support deserialization:
42 #
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.
50 #
51 # If `true`, serialize only the real data or non-Nit programs:
52 #
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
61
62 # Should strings declaring the objects type and attributes name be cached?
63 #
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.
67 #
68 # If `false`, the default, metadata strings are written as pure MessagePack
69 # strings, without their own metadata.
70 #
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
76
77 # List of the current open objects, the first is the main target of the serialization
78 #
79 # Used only when `plain_msgpack == true` to detect cycles in serialization.
80 private var open_objects = new Array[Object]
81
82 redef var current_object = null
83
84 redef fun serialize(object)
85 do
86 if object == null then
87 stream.write_msgpack_null
88 else
89 if plain_msgpack then
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
95 return
96 end
97 end
98
99 open_objects.add object
100 end
101
102 var last_object = current_object
103 current_object = object
104 object.accept_msgpack_serializer self
105 current_object = last_object
106
107 if plain_msgpack then open_objects.pop
108 end
109 end
110
111 redef fun serialize_attribute(name, value)
112 do
113 serialize_meta_string name
114 super
115 end
116
117 redef fun serialize_reference(object)
118 do
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)
123 else
124 # serialize
125 serialize object
126 end
127 end
128
129 private fun serialize_meta_string(type_name: String)
130 do
131 if plain_msgpack or not cache_metadata_strings then
132 # String only version
133 stream.write_msgpack_str type_name
134 return
135 end
136
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)
141 else
142 # serialize
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
147 end
148 end
149 end
150
151 # Serialization visitor to count attribute in `Serializable` objects
152 class AttributeCounter
153 super Serializer
154
155 # Number of attributes counted
156 var count = 0
157
158 redef fun serialize_attribute(name, object) do count += 1
159 end
160
161 # ---
162 # Services and serializables
163
164 redef class Writer
165 # Serialize `value` in MessagePack format
166 fun serialize_msgpack(value: nullable Serializable, plain: nullable Bool)
167 do
168 var serializer = new MsgPackSerializer(self)
169 serializer.plain_msgpack = plain or else false
170 serializer.serialize value
171 end
172 end
173
174 redef class Serializable
175
176 # Serialize `self` to MessagePack bytes
177 #
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
182 do
183 var stream = new BytesWriter
184 stream.serialize_msgpack(self, plain)
185 stream.close
186 return stream.bytes
187 end
188
189 # Hook to customize the serialization of this class to MessagePack
190 #
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`.
194 #
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)
198 do
199
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
204
205 if not v.plain_msgpack then
206
207 var n_meta_items = 2
208 if n_attributes > 0 then n_meta_items += 1
209 n_meta_items += msgpack_extra_array_items # obj+id, class_name, attributes
210
211 # Metadata
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
216
217 if n_attributes > 0 then v.stream.write_msgpack_map n_attributes
218 else
219 v.stream.write_msgpack_map n_attributes
220 end
221
222 v.serialize_core self
223 end
224
225 # Hook to customize the behavior of the `AttributeCounter`
226 #
227 # By default, this method makes `v` visits all serializable attributes.
228 protected fun accept_msgpack_attribute_counter(v: AttributeCounter)
229 do
230 v.serialize_core self
231 end
232
233 # Hook to request a larger than usual metadata array
234 #
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
238 end
239
240 redef class MsgPackExt
241 redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_ext(typ, data)
242 end
243
244 redef class Text
245 redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_str self
246 end
247
248 redef class Int
249 redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_int self
250 end
251
252 redef class Float
253 redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_double self
254 end
255
256 redef class Bool
257 redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_bool self
258 end
259
260 redef class Byte
261 redef fun accept_msgpack_serializer(v)
262 do
263 if v.plain_msgpack then
264 # Write as a string
265 v.stream.write_msgpack_int to_i
266 else
267 # Write as ext
268 var bytes = new Bytes.with_capacity(1)
269 bytes.add self.to_i
270 v.stream.write_msgpack_ext(v.ext_typ_byte, bytes)
271 end
272 end
273 end
274
275 redef class Char
276 redef fun accept_msgpack_serializer(v)
277 do
278 if v.plain_msgpack then
279 # Write as a string
280 v.stream.write_msgpack_fixstr to_s
281 else
282 # Write as ext
283 var bytes = to_s.to_bytes
284 v.stream.write_msgpack_ext(v.ext_typ_char, bytes)
285 end
286 end
287 end
288
289 redef class Bytes
290 redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_bin self
291 end
292
293 redef class CString
294 redef fun accept_msgpack_serializer(v) do to_s.accept_msgpack_serializer(v)
295 end
296
297 redef class SimpleCollection[E]
298 redef fun accept_msgpack_serializer(v)
299 do
300 if not v.plain_msgpack then
301 # Add metadata and other attributes
302 super
303 end
304
305 # Header
306 v.stream.write_msgpack_array length
307
308 # Items
309 for e in self do
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
314 end
315 end
316 end
317
318 redef fun msgpack_extra_array_items do return 1
319 end
320
321 redef class Map[K, V]
322 redef fun accept_msgpack_serializer(v)
323 do
324 if not v.plain_msgpack then
325 # Add metadata and other attributes
326 super
327 end
328
329 # Header
330 v.stream.write_msgpack_map keys.length
331
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
338 end
339
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
344 end
345 end
346 end
347
348 redef fun msgpack_extra_array_items do return 1
349 end