1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2013 Jean-Philippe Caissy <jpcaissy@piji.ca>
4 # Copyright 2013 Guillaume Auger <jeho@resist.ca>
5 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
11 # http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
19 # Phase generating methods to serialize Nit objects to different formats
20 module serialization_phase
22 private import parser_util
24 private import annotation
25 intrude import literal
27 redef class ToolContext
29 # Apply the annotation `serialize_as`
30 var serialization_phase_rename
: Phase = new SerializationPhaseRename(self, null)
32 # Generate serialization and deserialization methods on `auto_serializable` annotated classes.
33 var serialization_phase_pre_model
: Phase = new SerializationPhasePreModel(self,
34 [serialization_phase_rename
])
36 # The second phase of the serialization
37 var serialization_phase_post_model
: Phase = new SerializationPhasePostModel(self,
38 [modelize_property_phase
, serialization_phase_pre_model
])
42 # Is this node annotated to be made serializable?
43 private fun is_serialize
: Bool do return false
45 # Is this node annotated to not be made serializable?
46 private fun is_noserialize
: Bool do return false
49 redef class ADefinition
51 redef fun is_serialize
do
52 return get_annotations
("serialize").not_empty
or
53 get_annotations
("auto_serializable").not_empty
56 redef fun is_noserialize
do
57 return get_annotations
("noserialize").not_empty
61 private class SerializationPhaseRename
64 redef fun process_annotated_node
(node
, nat
)
66 var text
= nat
.n_atid
.n_id
.text
67 if text
!= "serialize_as" then return
69 if not node
isa AAttrPropdef then
70 toolcontext
.error
(node
.location
,
71 "Syntax Error: annotation `{text}` applies only to attributes.")
75 # Can't use `arg_as_string` because it needs the literal phase
77 if args
.length
!= 1 or not args
.first
isa AStringFormExpr then
78 toolcontext
.error
(node
.location
,
79 "Syntax Error: annotation `{text}` expects a single string literal as argument.")
83 var t
= args
.first
.collect_text
84 var val
= t
.substring
(1, t
.length-2
)
85 node
.serialize_name
= val
89 private class SerializationPhasePreModel
92 redef fun process_annotated_node
(node
, nat
)
94 # Skip if we are not interested
95 var text
= nat
.n_atid
.n_id
.text
96 var serialize
= text
== "auto_serializable" or text
== "serialize"
97 var noserialize
= text
== "noserialize"
98 if not (serialize
or noserialize
) then return
100 # Check legality of annotation
101 if node
isa AModuledecl then
102 if noserialize
then toolcontext
.error
(node
.location
, "Syntax Error: superfluous use of `{text}`, by default a module is `{text}`")
104 else if not (node
isa AStdClassdef or node
isa AAttrPropdef) then
105 toolcontext
.error
(node
.location
,
106 "Syntax Error: only a class, a module or an attribute can be annotated with `{text}`.")
108 else if serialize
and node
.is_noserialize
then
109 toolcontext
.error
(node
.location
,
110 "Syntax Error: an entity cannot be both `{text}` and `noserialize`.")
112 else if node
.as(Prod).get_annotations
(text
).length
> 1 then
113 toolcontext
.warning
(node
.location
, "useless-{text}",
114 "Warning: duplicated annotation `{text}`.")
117 # Check the `serialize` state of the parent
118 if not node
isa AModuledecl then
119 var up_serialize
= false
120 var up
: nullable ANode = node
125 else if up
.is_serialize
then
128 else if up
.is_noserialize
then
133 # Check for useless double declarations
134 if serialize
and up_serialize
then
135 toolcontext
.warning
(node
.location
, "useless-serialize",
136 "Warning: superfluous use of `{text}`.")
137 else if noserialize
and not up_serialize
then
138 toolcontext
.warning
(node
.location
, "useless-noserialize",
139 "Warning: superfluous use of `{text}`.")
144 redef fun process_nclassdef
(nclassdef
)
146 if not nclassdef
isa AStdClassdef then return
148 var serialize_by_default
= nclassdef
.how_serialize
150 if serialize_by_default
!= null then
152 # Add `super Serializable`
153 var sc
= toolcontext
.parse_superclass
("Serializable")
154 sc
.location
= nclassdef
.location
155 nclassdef
.n_propdefs
.add sc
158 var per_attribute
= not serialize_by_default
159 generate_serialization_method
(nclassdef
, per_attribute
)
160 generate_deserialization_init
(nclassdef
)
164 redef fun process_nmodule
(nmodule
)
166 # Clear the cache of constructors to review before adding to it
167 nmodule
.inits_to_retype
.clear
169 # collect all classes
170 var auto_serializable_nclassdefs
= nmodule
.auto_serializable_nclassdefs
171 if not auto_serializable_nclassdefs
.is_empty
then
172 generate_deserialization_method
(nmodule
, auto_serializable_nclassdefs
)
176 # Implement `core_serialize_to` on `nclassdef`
178 # Are attributes serialized on demand `per_attribute` with `serialize`?
179 # Otherwise they are serialized by default, and we check instead for `noserialize`.
180 fun generate_serialization_method
(nclassdef
: AClassdef, per_attribute
: Bool)
182 var npropdefs
= nclassdef
.n_propdefs
184 # Do not insert a `core_serialize_to` if it already exists
185 for npropdef
in npropdefs
do
186 if npropdef
isa AMethPropdef then
187 var methid
= npropdef
.n_methid
188 if methid
!= null and methid
.collect_text
== "core_serialize_to" then
194 var code
= new Array[String]
195 code
.add
"redef fun core_serialize_to(v)"
199 for attribute
in npropdefs
do if attribute
isa AAttrPropdef then
201 # Is `attribute` to be skipped?
202 if (per_attribute
and not attribute
.is_serialize
) or
203 attribute
.is_noserialize
then continue
205 code
.add
" v.serialize_attribute(\"{attribute.serialize_name}\
", {attribute.name})"
210 # Create method Node and add it to the AST
211 npropdefs
.push
(toolcontext
.parse_propdef
(code
.join
("\n")))
214 # Add an empty constructor to the automated nclassdef
216 # Will be filled by `SerializationPhasePostModel`.
217 fun generate_deserialization_init
(nclassdef
: AClassdef)
219 var npropdefs
= nclassdef
.n_propdefs
221 # Do not insert a `from_deserializer` if it already exists
222 for npropdef
in npropdefs
do
223 if npropdef
isa AMethPropdef then
224 var methid
= npropdef
.n_methid
225 if methid
!= null and methid
.collect_text
== "from_deserializer" then
232 redef init from_deserializer(v: Deserializer) do abort"""
234 var npropdef
= toolcontext
.parse_propdef
(code
).as(AMethPropdef)
235 npropdefs
.add npropdef
236 nclassdef
.parent
.as(AModule).inits_to_retype
.add npropdef
239 # Add an empty `Deserializer::deserialize_class_intern`
241 # Will be filled by `SerializationPhasePostModel`.
242 fun generate_deserialization_method
(nmodule
: AModule, nclassdefs
: Array[AStdClassdef])
244 var code
= new Array[String]
246 var deserializer_nclassdef
= nmodule
.deserializer_nclassdef
247 var deserializer_npropdef
248 if deserializer_nclassdef
== null then
250 code
.add
"redef class Deserializer"
251 deserializer_npropdef
= null
253 deserializer_npropdef
= deserializer_nclassdef
.deserializer_npropdef
256 if deserializer_npropdef
== null then
257 # create the property
258 code
.add
" redef fun deserialize_class_intern(name) do abort"
260 toolcontext
.error
(deserializer_npropdef
.location
, "Error: `Deserializer::deserialize_class_intern` is generated and must not be defined, use `deserialize_class` instead.")
264 if deserializer_nclassdef
== null then
266 nmodule
.n_classdefs
.add toolcontext
.parse_classdef
(code
.join
("\n"))
268 deserializer_nclassdef
.n_propdefs
.add
(toolcontext
.parse_propdef
(code
.join
("\n")))
273 private class SerializationPhasePostModel
276 # Fill the deserialization init `from_deserializer` and `Deserializer.deserialize_class_intern`
277 redef fun process_nmodule
(nmodule
)
279 for npropdef
in nmodule
.inits_to_retype
do
280 var nclassdef
= npropdef
.parent
281 assert nclassdef
isa AStdClassdef
283 var serialize_by_default
= nclassdef
.how_serialize
284 assert serialize_by_default
!= null
286 var per_attribute
= not serialize_by_default
287 fill_deserialization_init
(nclassdef
, npropdef
, per_attribute
)
290 # collect all classes
291 var auto_serializable_nclassdefs
= nmodule
.auto_serializable_nclassdefs
292 if not auto_serializable_nclassdefs
.is_empty
then
293 fill_deserialization_method
(nmodule
, auto_serializable_nclassdefs
)
297 # Fill the constructor to the generated `init_npropdef` of `nclassdef`
298 fun fill_deserialization_init
(nclassdef
: AClassdef, init_npropdef
: AMethPropdef, per_attribute
: Bool)
300 var code
= new Array[String]
302 redef init from_deserializer(v: Deserializer)
305 v.notify_of_creation self
308 for attribute
in nclassdef
.n_propdefs
do
309 if not attribute
isa AAttrPropdef then continue
311 # Is `attribute` to be skipped?
312 if (per_attribute
and not attribute
.is_serialize
) or
313 attribute
.is_noserialize
then continue
315 var mtype
= attribute
.mtype
316 if mtype
== null then continue
317 var type_name
= mtype
.to_s
318 var name
= attribute
.name
320 var resolved_type_name
= type_name
321 var mclassdef
= nclassdef
.mclassdef
322 if mclassdef
!= null then
323 var bound_mtype
= mclassdef
.bound_mtype
324 var resolved_mtype
= mtype
.resolve_for
(bound_mtype
, bound_mtype
, mclassdef
.mmodule
, true)
325 resolved_type_name
= resolved_mtype
.name
327 # TODO Use something like `V.class_name` to get the precise runtime type of virtual types.
328 # We currently use the upper bound of virtual types as static type in generated code
329 # for type suggestion and to prevent loading unexected types.
330 # This leaves a security issue when, for example, `DefaultMap::default_value`
331 # is bound to `nullable Object` and would allow any object to be loaded.
334 if type_name
== "nullable Object" then
337 self.{{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{resolved_type_name}}}")
341 var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{resolved_type_name}}}")
342 if v.deserialize_attribute_missing then
344 # What to do when an attribute is missing?
345 if attribute
.has_value
then
346 # Leave it to the default value
347 else if mtype
isa MNullableType then
349 self.{{{name}}} = null"""
351 v.errors.add new Error("Deserialization Error: attribute `{class_name}::{{{name}}}` missing from JSON object")"""
354 else if not {{{name}}} isa {{{type_name}}} then
355 v.errors.add new AttributeTypeError(self, "{{{attribute.serialize_name}}}", {{{name}}}, "{{{resolved_type_name}}}")
356 if v.keep_going == false then return
358 self.{{{name}}} = {{{name}}}
366 # Replace the body of the constructor
367 var npropdef
= toolcontext
.parse_propdef
(code
.join
("\n")).as(AMethPropdef)
368 init_npropdef
.n_block
= npropdef
.n_block
370 # Run the literal phase on the generated code
371 var v
= new LiteralVisitor(toolcontext
)
372 v
.enter_visit
(npropdef
.n_block
)
375 # Fill the abstract serialization service
376 fun fill_deserialization_method
(nmodule
: AModule, nclassdefs
: Array[AStdClassdef])
378 var deserializer_nclassdef
= nmodule
.deserializer_nclassdef
379 if deserializer_nclassdef
== null then return
380 var deserializer_npropdef
= deserializer_nclassdef
.deserializer_npropdef
381 if deserializer_npropdef
== null then return
383 # Collect local types expected to be deserialized
384 var types_to_deserialize
= new Set[String]
386 ## Local serializable standard class without parameters
387 for nclassdef
in nclassdefs
do
388 var mclass
= nclassdef
.mclass
389 if mclass
== null then continue
391 if mclass
.arity
== 0 and mclass
.kind
== concrete_kind
then
392 types_to_deserialize
.add mclass
.name
396 ## Static parametized types on serializable attributes
397 for nclassdef
in nmodule
.n_classdefs
do
398 if not nclassdef
isa AStdClassdef then continue
400 for attribute
in nclassdef
.n_propdefs
do
401 if not attribute
isa AAttrPropdef then continue
403 var serialize_by_default
= nclassdef
.how_serialize
404 if serialize_by_default
== null then continue
405 var per_attribute
= not serialize_by_default
407 # Is `attribute` to be skipped?
408 if (per_attribute
and not attribute
.is_serialize
) or
409 attribute
.is_noserialize
then continue
411 var mtype
= attribute
.mtype
412 if mtype
== null then continue
413 if mtype
isa MNullableType then mtype
= mtype
.mtype
415 if mtype
isa MClassType and mtype
.mclass
.arity
> 0 and
416 mtype
.mclass
.kind
== concrete_kind
and not mtype
.need_anchor
then
418 # Check is a `Serializable`
419 var mmodule
= nmodule
.mmodule
420 if mmodule
== null then continue
422 var greaters
= mtype
.mclass
.in_hierarchy
(mmodule
).greaters
423 var is_serializable
= false
424 for sup
in greaters
do if sup
.name
== "Serializable" then
425 is_serializable
= true
429 if is_serializable
then types_to_deserialize
.add mtype
.to_s
434 # Build implementation code
435 var code
= new Array[String]
436 code
.add
"redef fun deserialize_class_intern(name)"
439 for name
in types_to_deserialize
do
440 code
.add
" if name == \"{name}\
" then return new {name}.from_deserializer(self)"
443 code
.add
" return super"
446 # Replace the body of the constructor
447 var npropdef
= toolcontext
.parse_propdef
(code
.join
("\n")).as(AMethPropdef)
448 deserializer_npropdef
.n_block
= npropdef
.n_block
450 # Run the literal phase on the generated code
451 var v
= new LiteralVisitor(toolcontext
)
452 v
.enter_visit
(npropdef
.n_block
)
456 redef class AAttrPropdef
457 private fun name
: String do return n_id2
.text
459 # Name of this attribute in the serialized format
460 private var serialize_name
: String = name
is lazy
464 private fun type_name
: String
466 var name
= n_qid
.n_id
.text
468 if n_kwnullable
!= null then name
= "nullable {name}"
471 if not types
.is_empty
then
472 var params
= new Array[String]
473 for t
in types
do params
.add
(t
.type_name
)
474 return "{name}[{params.join(", ")}]"
480 private fun deserializer_nclassdef
: nullable AStdClassdef
482 for nclassdef
in n_classdefs
do
483 if not nclassdef
isa AStdClassdef then continue
484 var n_qid
= nclassdef
.n_qid
485 if n_qid
!= null and n_qid
.n_id
.text
== "Deserializer" then return nclassdef
491 private var inits_to_retype
= new Array[AMethPropdef]
493 redef fun is_serialize
495 var n_moduledecl
= n_moduledecl
496 return n_moduledecl
!= null and n_moduledecl
.is_serialize
499 # `AStdClassdef` marked as serializable, itself or one of theur attribute
500 private var auto_serializable_nclassdefs
: Array[AStdClassdef] is lazy
do
501 var array
= new Array[AStdClassdef]
502 for nclassdef
in n_classdefs
do
503 if nclassdef
isa AStdClassdef and nclassdef
.how_serialize
!= null then
511 redef class AStdClassdef
512 private fun deserializer_npropdef
: nullable AMethPropdef
514 for npropdef
in n_propdefs
do if npropdef
isa AMethPropdef then
515 var id
= npropdef
.n_methid
516 if id
isa AIdMethid and id
.n_id
.text
== "deserialize_class_intern" then
524 # Is this classed marked `serialize`? in part or fully?
526 # This method returns 3 possible values:
527 # * `null`, this class is not to be serialized.
528 # * `true`, the attributes of this class are to be serialized by default.
529 # * `false`, the attributes of this class are to be serialized on demand only.
530 fun how_serialize
: nullable Bool
532 # Is there a declaration on the classdef or the module?
533 var serialize
= is_serialize
535 if not serialize
and not is_noserialize
then
536 # Is the module marked serialize?
537 serialize
= parent
.as(AModule).is_serialize
540 if serialize
then return true
542 if not serialize
then
543 # Is there an attribute marked serialize?
544 for npropdef
in n_propdefs
do
545 if npropdef
.is_serialize
then