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 # Phase generating methods (code) to serialize Nit objects
16 module serialization_code_gen_phase
18 intrude import serialization_model_phase
20 redef class ToolContext
22 # The second phase of the serialization
23 var serialization_phase_post_model
: Phase = new SerializationPhasePostModel(self,
24 [modelize_property_phase
, serialization_phase_pre_model
])
27 private class SerializationPhasePostModel
30 # Fill the deserialization init `from_deserializer` and `Deserializer.deserialize_class_intern`
31 redef fun process_nmodule
(nmodule
)
33 for npropdef
in nmodule
.inits_to_retype
do
34 var nclassdef
= npropdef
.parent
35 assert nclassdef
isa AStdClassdef
37 var serialize_by_default
= nclassdef
.how_serialize
38 assert serialize_by_default
!= null
40 var per_attribute
= not serialize_by_default
41 fill_deserialization_init
(nclassdef
, npropdef
, per_attribute
)
45 var auto_serializable_nclassdefs
= nmodule
.auto_serializable_nclassdefs
46 if not auto_serializable_nclassdefs
.is_empty
then
47 fill_deserialization_method
(nmodule
, auto_serializable_nclassdefs
)
51 # Fill the constructor to the generated `init_npropdef` of `nclassdef`
52 fun fill_deserialization_init
(nclassdef
: AClassdef, init_npropdef
: AMethPropdef, per_attribute
: Bool)
54 var code
= new Array[String]
56 redef init from_deserializer(v: Deserializer)
59 v.notify_of_creation self
62 for attribute
in nclassdef
.n_propdefs
do
63 if not attribute
isa AAttrPropdef then continue
65 # Is `attribute` to be skipped?
66 if (per_attribute
and not attribute
.is_serialize
) or
67 attribute
.is_noserialize
then continue
69 var mtype
= attribute
.mtype
70 if mtype
== null then continue
71 var type_name
= mtype
.to_s
72 var name
= attribute
.name
74 var resolved_type_name
= type_name
75 var mclassdef
= nclassdef
.mclassdef
76 if mclassdef
!= null then
77 var bound_mtype
= mclassdef
.bound_mtype
78 var resolved_mtype
= mtype
.resolve_for
(bound_mtype
, bound_mtype
, mclassdef
.mmodule
, true)
79 resolved_type_name
= resolved_mtype
.name
81 # TODO Use something like `V.class_name` to get the precise runtime type of virtual types.
82 # We currently use the upper bound of virtual types as static type in generated code
83 # for type suggestion and to prevent loading unexected types.
84 # This leaves a security issue when, for example, `DefaultMap::default_value`
85 # is bound to `nullable Object` and would allow any object to be loaded.
88 if type_name
== "nullable Object" then
91 self.{{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{resolved_type_name}}}")
95 var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{resolved_type_name}}}")
96 if v.deserialize_attribute_missing then
98 # What to do when an attribute is missing?
99 if attribute
.has_value
then
100 # Leave it to the default value
101 else if mtype
isa MNullableType then
103 self.{{{name}}} = null"""
105 v.errors.add new Error("Deserialization Error: attribute `{class_name}::{{{name}}}` missing from JSON object")"""
108 else if not {{{name}}} isa {{{type_name}}} then
109 v.errors.add new AttributeTypeError(self, "{{{attribute.serialize_name}}}", {{{name}}}, "{{{resolved_type_name}}}")
110 if v.keep_going == false then return
112 self.{{{name}}} = {{{name}}}
120 # Replace the body of the constructor
121 var npropdef
= toolcontext
.parse_propdef
(code
.join
("\n")).as(AMethPropdef)
122 init_npropdef
.n_block
= npropdef
.n_block
124 # Run the literal phase on the generated code
125 var v
= new LiteralVisitor(toolcontext
)
126 v
.enter_visit
(npropdef
.n_block
)
129 # Fill the abstract serialization service
130 fun fill_deserialization_method
(nmodule
: AModule, nclassdefs
: Array[AStdClassdef])
132 var deserializer_nclassdef
= nmodule
.deserializer_nclassdef
133 if deserializer_nclassdef
== null then return
134 var deserializer_npropdef
= deserializer_nclassdef
.deserializer_npropdef
135 if deserializer_npropdef
== null then return
137 # Collect local types expected to be deserialized
138 var types_to_deserialize
= new Set[String]
140 ## Local serializable standard class without parameters
141 for nclassdef
in nclassdefs
do
142 var mclass
= nclassdef
.mclass
143 if mclass
== null then continue
145 if mclass
.arity
== 0 and mclass
.kind
== concrete_kind
then
146 types_to_deserialize
.add mclass
.name
150 ## Static parametized types on serializable attributes
151 for nclassdef
in nmodule
.n_classdefs
do
152 if not nclassdef
isa AStdClassdef then continue
154 for attribute
in nclassdef
.n_propdefs
do
155 if not attribute
isa AAttrPropdef then continue
157 var serialize_by_default
= nclassdef
.how_serialize
158 if serialize_by_default
== null then continue
159 var per_attribute
= not serialize_by_default
161 # Is `attribute` to be skipped?
162 if (per_attribute
and not attribute
.is_serialize
) or
163 attribute
.is_noserialize
then continue
165 var mtype
= attribute
.mtype
166 if mtype
== null then continue
167 if mtype
isa MNullableType then mtype
= mtype
.mtype
169 if mtype
isa MClassType and mtype
.mclass
.arity
> 0 and
170 mtype
.mclass
.kind
== concrete_kind
and not mtype
.need_anchor
then
172 # Check is a `Serializable`
173 var mmodule
= nmodule
.mmodule
174 if mmodule
== null then continue
176 var greaters
= mtype
.mclass
.in_hierarchy
(mmodule
).greaters
177 var is_serializable
= false
178 for sup
in greaters
do if sup
.name
== "Serializable" then
179 is_serializable
= true
183 if is_serializable
then types_to_deserialize
.add mtype
.to_s
188 # Build implementation code
189 var code
= new Array[String]
190 code
.add
"redef fun deserialize_class_intern(name)"
193 for name
in types_to_deserialize
do
194 code
.add
" if name == \"{name}\
" then return new {name}.from_deserializer(self)"
197 code
.add
" return super"
200 # Replace the body of the constructor
201 var npropdef
= toolcontext
.parse_propdef
(code
.join
("\n")).as(AMethPropdef)
202 deserializer_npropdef
.n_block
= npropdef
.n_block
204 # Run the literal phase on the generated code
205 var v
= new LiteralVisitor(toolcontext
)
206 v
.enter_visit
(npropdef
.n_block
)